[spray-json] Cannot convert nested Maps to/from Json

6,312 views
Skip to first unread message

Evan Chan

unread,
Nov 20, 2012, 1:46:31 PM11/20/12
to spray...@googlegroups.com
I'm using spray-json 1.2.2 and cannot convert a nested Map:

scala> Map("a" -> Map("b" -> 2), "b" -> Map()).toJson
<console>:14: error: Cannot find JsonWriter or JsonFormat type class for scala.collection.immutable.Map[String,scala.collection.immutable.Map[_ <: String, Int]]
              Map("a" -> Map("b" -> 2), "b" -> Map()).toJson


I don't completely understand why because a Map() as the V value is still of type RootJsonFormat, which is subclassed from JsonFormat.   After all, a Seq(Map()) works, and a Seq(Seq()) works too.

I've opened an issue:

Seems like it should be fixable with lazyFormat or a patched map type, I'll try it out and report back.

-Evan

Evan Chan

unread,
Nov 20, 2012, 2:00:31 PM11/20/12
to spray...@googlegroups.com
Oh, I discovered the real problem is that Map() is not really typed.   

Is there any way to convert a Map where the value can be any JsonFormat-inspired type, but the exact type is not known at compile time?

-Evan

Evan Chan

unread,
Nov 20, 2012, 3:49:50 PM11/20/12
to spray...@googlegroups.com
I have an implicit format for converting a Map of [String, Any].....   anybody else interested in this, or want to offer feedback?

  implicit object AnyJsonFormat extends JsonFormat[Any] {
    def write(x: Any) = x match {
      case n: Int => JsNumber(n)
      case s: String => JsString(s)
      case x: Seq[_] => seqFormat[Any].write(x)
      case m: Map[String, _] => mapFormat[String, Any].write(m)
      case b: Boolean if b == true => JsTrue
      case b: Boolean if b == false => JsFalse
      case x => serializationError("Do not understand object of type " + x.getClass.getName)
    }
    def read(value: JsValue) = value match {
      case JsNumber(n) => n.intValue()
      case JsString(s) => s
      case a: JsArray => listFormat[Any].read(value)
      case o: JsObject => mapFormat[String, Any].read(value)
      case JsTrue => true
      case JsFalse => false
      case x => deserializationError("Do not understand how to deserialize " + x)
    }
  }


--
 
 



--
--
Evan Chan
Senior Software Engineer | 
e...@ooyala.com | (650) 996-4600
www.ooyala.com | blog | @ooyala

Mathias

unread,
Nov 21, 2012, 3:46:09 AM11/21/12
to spray...@googlegroups.com
Evan,

note that, while your `JsonFormat[Any]` certainly works, you forego all the advantages of compile-time verification of your (de)serialization code.
Therefore you should only fallback to runtime type analysis if the structures you are working truly are unknown at compile time.

Cheers,
Mathias

---
mat...@spray.io
http://spray.io
> --
>
>

Evan chan

unread,
Nov 21, 2012, 10:13:37 AM11/21/12
to spray...@googlegroups.com, spray...@googlegroups.com
Yeah, my app inputs are json objects whose values may be different types, the only possible predictability is that the type for each field may be known, but the fields can expand at any point. It's too dynamic to make say case class extraction possible (would have to make an app restart or something with every field expansion-- not workable).

One possible optimization is to not deserialize the values -- leave them as JsValue.

-Evan
Carry your candle, run to the darkness
Seek out the helpless, deceived and poor
Hold out your candle for all to see it
Take your candle, and go light your world
> --
>
>

pavel.numbe...@gmail.com

unread,
Mar 2, 2016, 8:41:28 AM3/2/16
to spray.io User List, e...@ooyala.com
I kind of do; notice that n of JsNumber(n) is scala.math.BigDecimal; calling intValue on int you run the risk of getting a different number whatsoever if n is out of range for Int.
I did this instead (not sure if this one is an ideal solution either):
      case JsNumber(n) {    // scala.math.BigDecimal
       
try {
          n
.toIntExact
       
} catch {
         
case _: java.lang.ArithmeticException {
           
try {
              n
.toLongExact
           
} catch {
             
case _: java.lang.ArithmeticException n.toDouble
           
}
         
}
       
}
     
}

Meet Vadera

unread,
Jul 12, 2016, 8:27:00 AM7/12/16
to spray.io User List, e...@ooyalamail.com
Hi,

Is there any update on this bug? I am trying this today also with v1.3.2 but still it's showing me an error. I tried to understand case classes but all in vain. Will be glad if someone can help me convert  Map[String, Map[String, Any]] or Map [String, Map[String, String]] to a valid JSON string.

Thanks,
Meet.

Alessandro Badin

unread,
Dec 23, 2016, 3:29:02 AM12/23/16
to spray.io User List, e...@ooyalamail.com
In my case all nested Maps were the type Map[String, String], then I could resolve this using the following JsonFormat:

json string = {"a": "1", "b": "2", "c": {"d": "4", "e": "5"}}
object representation = Map(a -> 1, b -> 2, c -> Map(d -> 4, e -> 5))

Code:
    implicit object StateEventJsonFormat extends RootJsonFormat[Map[String, Any]] {
      def write(c: Map[String, Any]) = ???
      def read(value: JsValue) = value match {
        case x: JsObject => x.fields match {
          case x: Map[String, JsValue] => x transform {
            case (a: String, b: JsObject) => read(b)
            case (a: String, JsString(s)) => s
            case (a: String, _) => null
          }
        }
        case _ => null
      }
    }

Best Regards
Reply all
Reply to author
Forward
0 new messages