Skip to content

Instantly share code, notes, and snippets.

@SriramKeerthi
Last active August 29, 2015 14:08
Show Gist options
  • Save SriramKeerthi/c7de651d1801f22212b9 to your computer and use it in GitHub Desktop.
Save SriramKeerthi/c7de651d1801f22212b9 to your computer and use it in GitHub Desktop.
JSON to Map parser for Scala
/**
* 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
@SriramKeerthi
Copy link
Author

Someone more experienced in Scala could probably drop several lines of code from this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment