How do you serialize a Map to JSON in Scala?
Asked Answered
V

7

29

So I have a Map in Scala like this:

val m = Map[String, String](
    "a" -> "theA",
    "b" -> "theB",
    "c" -> "theC",
    "d" -> "theD",
    "e" -> "theE"
)

and I want to serialize this structure into a JSON string using lift-json.

Do any of you know how to do this?

Vacuole answered 7/6, 2011 at 20:43 Comment(0)
L
28

How about this?

implicit val formats = net.liftweb.json.DefaultFormats
import net.liftweb.json.JsonAST._
import net.liftweb.json.Extraction._
import net.liftweb.json.Printer._
val m = Map[String, String](
    "a" -> "theA",
    "b" -> "theB",
    "c" -> "theC",
    "d" -> "theD",
    "e" -> "theE"
)
println(compact(render(decompose(m))))

output:

{"e":"theE","a":"theA","b":"theB","c":"theC","d":"theD"}

EDIT:

For a scala.collections.mutable.Map, you should convert it first to an immutable map: .toMap

Lyle answered 7/6, 2011 at 20:57 Comment(2)
When I change the type to scala.collections.mutable.Map, the output becomes: [{"_1":"d","_2":"theD"},{"_1":"a","_2":"theA"},{"_1":"c","_2":"theC"},{"_1":"b","_2":"theB"},{"_1":"e","_2":"theE"}], which makes me kind of sad.Vacuole
There is an easy workaround for this -- just call .toMap on the mutable map to get an immutable map, and then things work okay again.Vacuole
R
37

If you are using the latest Scala 2.10.x and above :

println(scala.util.parsing.json.JSONObject(m))
Raid answered 24/1, 2015 at 14:20 Comment(5)
It's been removed from Scala 2.11 and above.Coexecutor
@Coexecutor looks like it's still there : scala-lang.org/api/2.11.8/scala-parser-combinators/…Thrombosis
I might make a mistake somewhere. But were you able to use it? What version of scala-parser-combinators did you use (a full maven dependency would be helpful)?Coexecutor
It looks like it doesn't recurse correctly into the object. For example, Map("0" -> 1, "1" -> List(Map("2" -> 3))) is stringified to {"0" : 1, "1" : List(Map(2 -> 3))}.Rete
Beware, it does not recurse, and will translate nested objects to their string representation!Cost
L
28

How about this?

implicit val formats = net.liftweb.json.DefaultFormats
import net.liftweb.json.JsonAST._
import net.liftweb.json.Extraction._
import net.liftweb.json.Printer._
val m = Map[String, String](
    "a" -> "theA",
    "b" -> "theB",
    "c" -> "theC",
    "d" -> "theD",
    "e" -> "theE"
)
println(compact(render(decompose(m))))

output:

{"e":"theE","a":"theA","b":"theB","c":"theC","d":"theD"}

EDIT:

For a scala.collections.mutable.Map, you should convert it first to an immutable map: .toMap

Lyle answered 7/6, 2011 at 20:57 Comment(2)
When I change the type to scala.collections.mutable.Map, the output becomes: [{"_1":"d","_2":"theD"},{"_1":"a","_2":"theA"},{"_1":"c","_2":"theC"},{"_1":"b","_2":"theB"},{"_1":"e","_2":"theE"}], which makes me kind of sad.Vacuole
There is an easy workaround for this -- just call .toMap on the mutable map to get an immutable map, and then things work okay again.Vacuole
J
9

You can roll your own pretty easily (yay, no dependencies). This one does basic handling of types and will do recursion unlike JSONObject that was mentioned:

import scala.collection.mutable.ListBuffer

object JsonConverter {
  def toJson(o: Any) : String = {
    var json = new ListBuffer[String]()
    o match {
      case m: Map[_,_] => {
        for ( (k,v) <- m ) {
          var key = escape(k.asInstanceOf[String])
          v match {
            case a: Map[_,_] => json += "\"" + key + "\":" + toJson(a)
            case a: List[_] => json += "\"" + key + "\":" + toJson(a)
            case a: Int => json += "\"" + key + "\":" + a
            case a: Boolean => json += "\"" + key + "\":" + a
            case a: String => json += "\"" + key + "\":\"" + escape(a) + "\""
            case _ => ;
          }
        }
      }
      case m: List[_] => {
        var list = new ListBuffer[String]()
        for ( el <- m ) {
          el match {
            case a: Map[_,_] => list += toJson(a)
            case a: List[_] => list += toJson(a)
            case a: Int => list += a.toString()
            case a: Boolean => list += a.toString()
            case a: String => list += "\"" + escape(a) + "\""
            case _ => ;
          }
        }
        return "[" + list.mkString(",") + "]"
      }
      case _ => ;
    }
    return "{" + json.mkString(",") + "}"
  }

  private def escape(s: String) : String = {
    return s.replaceAll("\"" , "\\\\\"");
  }
}

You can see it in action like

println(JsonConverter.toJson(
    Map("a"-> 1,
        "b" -> Map(
            "nes\"ted" -> "yeah{\"some\":true"),
            "c" -> List(
                1,
                2,
                "3",
                List(
                    true,
                    false,
                    true,
                    Map(
                        "1"->"two",
                        "3"->"four"
                    )
                )
            )
        )
    )
)

{"a":1,"b":{"nes\"ted":"yeah{\"some\":true"},"c":[1,2,"3",[true,false,true,{"1":"two","3":"four"}]]}

(It's part of a Coinbase GDAX library I've written, see util.scala)

Juna answered 14/2, 2017 at 4:9 Comment(0)
C
6

You can use this simple way if you are using play framework:

import play.api.libs.json._

Json.toJson(<your_map>)
Courcy answered 15/2, 2017 at 20:53 Comment(0)
F
2

This code will convert many different objects, and doesn't require any libraries beyond the built-in the scala.util.parsing.json._. It won't properly handle edge cases like Maps with integers as keys.

import scala.util.parsing.json.{JSONArray, JSONObject}
def toJson(arr: List[Any]): JSONArray = {
  JSONArray(arr.map {
    case (innerMap: Map[String, Any]) => toJson(innerMap)
    case (innerArray: List[Any])      => toJson(innerArray)
    case (other)                      => other
  })
}
def toJson(map: Map[String, Any]): JSONObject = {
  JSONObject(map.map {
    case (key, innerMap: Map[String, Any]) =>
      (key, toJson(innerMap))
    case (key, innerArray: List[Any]) =>
      (key, toJson(innerArray))
    case (key, other) =>
      (key, other)
  })
}
Flowerlike answered 9/5, 2017 at 0:52 Comment(0)
B
1

Similar to Einar's solution, you can use JSONObject from Parser Combinators to do this. Note that it does not recurse, you'll need to do this yourself. The library also includes JSONArray, for list like data structures. Something like the following will address Noel's concerns about nested structures. This example does not recurse to an arbitrary level, but will handle a value of List[Map[String, Any]].

import scala.util.parsing.json.{JSONArray, JSONObject}

def toJson(m : Map[String, Any]): String = JSONObject(
  m.mapValues {
    case mp: Map[String, Any] => JSONObject(mp)
    case lm: List[Map[String, Any]] => JSONArray(lm.map(JSONObject(_)))
    case x => x
    }
  ).toString
Bookstack answered 13/2, 2017 at 20:40 Comment(0)
M
0

Supplementing the answer by @Raja.

For those nested object, I locally modify the class to have my wanted toString() like this way:

case class MList[T]() extends MutableList[T] { override def toString() = "[" + this.toList.mkString(",") + "]" }

Then inside the Map object, I use this MList instead of the standard List. That way, my map object prints out fine by calling JSONObject(map).toString().

Marinetti answered 18/5, 2017 at 1:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.