spray-json icon indicating copy to clipboard operation
spray-json copied to clipboard

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

Open ontologiae opened this issue 7 years ago • 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)

ontologiae avatar Jan 10 '17 13:01 ontologiae

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]

klpx avatar Feb 06 '17 09:02 klpx

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

l15k4 avatar Dec 06 '17 22:12 l15k4

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]]]
}

lustefaniak avatar Dec 07 '17 10:12 lustefaniak

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

lustefaniak avatar Dec 07 '17 10:12 lustefaniak

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 :-)

l15k4 avatar Dec 07 '17 10:12 l15k4

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 avatar Mar 22 '19 02:03 chenshaoxing

@chenshaoxing you crossposted at https://github.com/spray/spray-json/issues/292, I answered there.

raboof avatar Mar 22 '19 10:03 raboof