spray/spray-json

Error : spray.json.SerializationException: Map key must be formatted as JsString, not '["A","b"]'

ontologiae opened this issue · 7 comments

Hi, I defined an object as follow :
case class Obj(a: String, b: Map[String, List[String]], c: String, d : Int, f : Map[(String,String), (Float,Float)])

All the config for spray worked until I add Map[(String,String), (Float,Float)]

println(Obj("id", Map( "Rule1" -> List("el1","el2")), "srcRep", 1, Map( (("A","b")) -> ((0.5f,1.2f)) ) ).toJson)
Here's the stacktrace :

Exception in thread "main" spray.json.SerializationException: Map key must be formatted as JsString, not '["A","b"]'
	at spray.json.CollectionFormats$$anon$3$$anonfun$write$3.apply(CollectionFormats.scala:53)
	at spray.json.CollectionFormats$$anon$3$$anonfun$write$3.apply(CollectionFormats.scala:50)
	at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
	at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
	at scala.collection.immutable.Map$Map1.foreach(Map.scala:116)
	at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
	at scala.collection.AbstractTraversable.map(Traversable.scala:104)
	at spray.json.CollectionFormats$$anon$3.write(CollectionFormats.scala:50)
	at spray.json.CollectionFormats$$anon$3.write(CollectionFormats.scala:48)
	at spray.json.ProductFormats$class.productElement2Field(ProductFormats.scala:46)
	at com.xxxxxx.JsonProtocols$.productElement2Field(JsonConversion.scala:6)
	at spray.json.ProductFormatsInstances$$anon$5.write(ProductFormatsInstances.scala:126)
	at spray.json.ProductFormatsInstances$$anon$5.write(ProductFormatsInstances.scala:118)
	at spray.json.PimpedAny.toJson(package.scala:39)
	at com.xxxxxxx.MainGenere$.delayedEndpoint$com$xxxxxx$xxxxx$MainGenere$1(MainGenere.scala:415)
klpx commented

Tuple2 is encoded as JSON array. And JSON array could not be an JSON object key.
You can create your own JsonFormat[(String, String)] which will serialize tuple as string ("str1,str2" for example), but in this case you can't import all of DefaultJsonFormat._ because it already have deserizlier for Tuple2[T1 : JsonFormat, T2 : JsonFormat]

l15k4 commented

For me this workaround doesn't work :

object Foo extends DefaultJsonProtocol {
  import spray.json._

  case class CampKey(value: String) extends AnyVal
  case class DataPointKey(value: String) extends AnyVal
  case class DataPointValue(value: String) extends AnyVal
  type DataMap                = Map[CampKey, Map[DataPointKey, Set[DataPointValue]]]

  implicit object dataPointKeyFormat extends RootJsonReader[DataPointKey] { def read(json: JsValue): DataPointKey = DataPointKey(json.asInstanceOf[JsString].value) }
  implicit object dataPointValueFormat extends RootJsonReader[DataPointValue] { def read(json: JsValue): DataPointValue = DataPointValue(json.asInstanceOf[JsString].value) }
  implicit object campKeyFormat extends RootJsonReader[CampKey] { def read(json: JsValue): CampKey = CampKey(json.asInstanceOf[JsString].value) }

  implicitly[JsonReader[DataMap]] //  Cannot find JsonReader or JsonFormat type class for DataMap

But JsonReader implicit is not resolved for DataMap

That would work:

object Foo {
  import spray.json._
  case class CampKey(value: String)        extends AnyVal
  case class DataPointKey(value: String)   extends AnyVal
  case class DataPointValue(value: String) extends AnyVal
  type DataMap = Map[CampKey, Map[DataPointKey, Set[DataPointValue]]]

  implicit object dataPointKeyFormat extends RootJsonFormat[DataPointKey] {
    def read(json: JsValue): DataPointKey          = DataPointKey(json.asInstanceOf[JsString].value)
    override def write(obj: DataPointKey): JsValue = JsString(obj.value)
  }
  implicit object dataPointValueFormat extends RootJsonFormat[DataPointValue] {
    def read(json: JsValue): DataPointValue          = DataPointValue(json.asInstanceOf[JsString].value)
    override def write(obj: DataPointValue): JsValue = JsString(obj.value)
  }
  implicit object campKeyFormat extends RootJsonFormat[CampKey] {
    def read(json: JsValue): CampKey          = CampKey(json.asInstanceOf[JsString].value)
    override def write(obj: CampKey): JsValue = JsString(obj.value)
  }

  implicit val dataPointValueSetFormat = new RootJsonFormat[Set[DataPointValue]] {
    def write(items: Set[DataPointValue]) = JsArray(items.map(_.toJson).toVector)
    def read(value: JsValue) = value match {
      case JsArray(elements) => elements.map(_.convertTo[DataPointValue]).toSet[DataPointValue]
      case x                 => deserializationError("Expected Array as JsArray, but got " + x)
    }
  }

  implicit val dataMapEntryFormat = DefaultJsonProtocol.mapFormat[DataPointKey, Set[DataPointValue]]
  implicit val dataMapRootReader  = DefaultJsonProtocol.mapFormat[CampKey, Map[DataPointKey, Set[DataPointValue]]]
}

And you should probably change reads, to get rid of asInstanceOf, and fail with DeserializationException using deserializationError if value is not JsString.

l15k4 commented

Yeah I did not want to discourage people from reading it as they usually give up on snippets longer that xx lines and I would decrease my changes on getting help :-)

import spray.json._
import spray.json.DefaultJsonProtocol._

val list = Map("k"->"f","n"->"r","d"->ListInt)
println(list.toJson)

compiler error: Cannot find JsonWriter or JsonFormat type class for scala.collection.immutable.Map[String,java.io.Serializable]

I used wrong?

@chenshaoxing you crossposted at #292, I answered there.