Last active
August 29, 2015 14:08
-
-
Save SriramKeerthi/c7de651d1801f22212b9 to your computer and use it in GitHub Desktop.
JSON to Map parser for Scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* After pulling my hair out trying to find a clean, small utility to parse JSON to Maps and back in Scala, | |
* I finally decided to break out some elbow grease and write my own. | |
* | |
* This is what I came up with, and it took some trial and error to come up with this approach. It has lots | |
* of room for improvement, so feel free to help out! | |
*/ | |
package com.caffinc.utils | |
import scala.util.{Success, Try} | |
/* | |
Utility to convert a JSON String to a Map and a Map to a JSON String | |
*/ | |
object JsonUtils { | |
private val stringPattern = """([\"\'])(.*)([\"\'])""".r | |
private val mapPattern = """(\{)(.*)(\})""".r | |
private val arrayPattern = """(\[)(.*)(\])""".r | |
// Converts a Map[String, Any] to a JSON String, supports nested Maps | |
def toJson(map: Map[String, Any]): String = { | |
var returnVal = "{" | |
map.keys.foreach(k => { | |
returnVal = returnVal + "\"" + k + "\":" + objToJson(map(k)) + "," | |
}) | |
return (if(returnVal == "{") returnVal else returnVal.dropRight(1)) + "}" | |
} | |
// Converts a JSON String to a Map[String, Any] | |
// Numbers are converted to Long or Double | |
// All collections are List[Any] | |
// Nested elements are Map[String, Any] | |
def toMap(json: String): Map[String, Any] = { | |
var returnVal = Map[String,Any]() | |
val tokens = tokenize(json.trim) | |
tokens.foreach(token => { | |
if (token.length > 0) | |
returnVal = returnVal + keyValue(token) | |
}) | |
return returnVal | |
} | |
private def objToJson(obj: Any): String = { | |
var returnVal = "" | |
obj match { | |
case e:Number => | |
returnVal = e.toString | |
case e:String => | |
returnVal = "\"" + e + "\"" | |
case e:List[Any] => | |
returnVal = "[" | |
e.foreach(x => { | |
returnVal = returnVal + objToJson(x) + "," | |
}) | |
returnVal = (if (returnVal == "[") returnVal else returnVal.dropRight(1)) + "]" | |
case e:Map[_,_] => | |
returnVal = toJson(e.asInstanceOf[Map[String,Any]]) | |
case e:Any => | |
returnVal = e.toString | |
} | |
return returnVal | |
} | |
private def tokenize(json: String): List[String] = { | |
var tokens = List[String]() | |
var level = 0 | |
var inString = false | |
var flag = false | |
var lastIndex = 1 | |
for (i <- 0 until json.length) { | |
if (json.charAt(i) == '{' || json.charAt(i) == '[') { | |
level = level + 1 | |
flag = false | |
} | |
else if (json.charAt(i) == '}' || json.charAt(i) == ']') { | |
level = level - 1 | |
flag = false | |
} | |
else if (json.charAt(i) == '\\') { | |
flag = !flag | |
} | |
else if (json.charAt(i) == '\"') { | |
if (flag) { | |
flag = false | |
} | |
else { | |
inString = !inString | |
} | |
} | |
else if (json.charAt(i) == ',' && !inString && level == 1) { | |
tokens = json.substring(lastIndex, i).trim :: tokens | |
lastIndex = i + 1 | |
} | |
else { | |
flag = false | |
} | |
} | |
tokens = json.substring(lastIndex).trim.dropRight(1) :: tokens | |
return tokens.reverse | |
} | |
private def keyValue(json: String): (String, Any) = { | |
var flag = false | |
var inString = false | |
for(i <- 0 until json.length) { | |
if (json.charAt(i) == '\\') { | |
flag = !flag | |
} | |
else if (json.charAt(i) == '"' || json.charAt(i) == '\'') { | |
if (flag) { | |
flag = false | |
} | |
else { | |
inString = !inString | |
} | |
} | |
else if (json.charAt(i) == ':' && !inString) { | |
var key = json.substring(0, i).trim | |
key = if (key.startsWith("\"") || key.startsWith("\'")) key.drop(1) else key | |
key = if (key.endsWith("\"") || key.endsWith("\'")) key.dropRight(1) else key | |
return (key, getValue(json.substring(i + 1).trim)) | |
} | |
else { | |
flag = false | |
} | |
} | |
throw new InvalidJSONException | |
} | |
private def getValue(json: String): Any = { | |
if (json.length == 0) { | |
return json | |
} | |
try { | |
return getNum(json.toDouble) | |
} catch { | |
case _:NumberFormatException => { | |
/* Do nothing */ | |
} | |
} | |
try { | |
return json.toBoolean | |
} catch { | |
case _:IllegalArgumentException => { | |
/* Do nothing */ | |
} | |
} | |
json match { | |
case stringPattern(left, str, right) => | |
str | |
case mapPattern(left, map, right) => | |
toMap(json) | |
case arrayPattern(left, array, right) => | |
for (token <- tokenize(json)) yield getValue(token) | |
case _ => | |
if (json == "null") { | |
null | |
} | |
else { | |
throw new InvalidJSONException | |
} | |
} | |
} | |
private def getNum(num: Double): Number = { | |
if (num.floor == num) num.toLong else num | |
} | |
def main(args: Array[String]): Unit = { | |
val json = " { \"test\": \"test\" , \"test2\": {\"test\":124.5, \"test2\": 123, \"test3\": [1,2,3]}, \"test4\":-123 } " | |
println(toMap(json)) | |
} | |
} | |
class InvalidJSONException extends RuntimeException |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Someone more experienced in Scala could probably drop several lines of code from this :)