Reputation: 800
I have a KG class defined like so (case class and companion object):
case class KG(a: Resource,
b: String,
c: Option[Resource],
d: Option[String],
d: Option[Seq[String]])
object KG {
implicit val writes: Writes[KG] = (o: KG) => Json.obj(
"a" -> o.resource.getURI,
"b" -> o.label,
"c" -> o.subClassOf.map(_.getURI),
"d" -> o.d
)
}
This is the output of my sbt console when trying to figure out what was happening
test: KG = KG(http://some-url-this-is,test,Some(http://another-url-this-is),None,None)
scala> val ontology = Seq(test)
ontology: Seq[KG] = List(KG(http://some-url-this-is,test,Some(http://another-url-this-is),None,None))
scala> val initial = ontology.groupBy(_.c.map(_.getLocalName))
initial: scala.collection.immutable.Map[Option[String],Seq[KG]] = Map(Some(Person) -> List(KG(http://some-url-this-is,test,Some(http://another-url-this-is),None,None))
scala> initial.getClass
res8: Class[_ <: scala.collection.immutable.Map[Option[String],Seq[KG]]] = class scala.collection.immutable.Map$Map1
scala> Json.toJson(initial)
res7: play.api.libs.json.JsValue = [["another-url-this-is",[{"a":"http://some-url-this-is","b":"test","c":"http://another-url-this-is","d":null}]]]
While initial
is a Map, it is serialized as an array.... Why is this? Have I misconfigured any serialization implicits?
[
[
"another-url-this-is",
[
{
"d": null,
"b": "test",
"a": "http://some-url-this-is",
"c": "http://another-url-this-is"
}
]
]
]
Upvotes: 1
Views: 228
Reputation: 20561
The short answer is that the only Writes[Map[_,_]]
Play JSON provides is for Map[String, V]
, as those are guaranteed to be serializable as a JSON object. For all other key types, the fact that a Map[K,V]
is an Iterable[(K, V)]
(if you have some experience with a Lisp, this is effectively like an "association list": a list of pairs) means that the default Writes[Iterable[(K, V)]]
takes effect. This serializes each (K, V)
as a JSON array of length 2 with the first value being the K
and the second the V
; all of these JSON arrays are then values in an outer JSON array.
This behavior arises from the fact that while the values in a JSON object can be any JSON value, the keys must be strings. In the particular case of Option[String]
, this doesn't work since there is no JSON string that one could use to represent None
that wouldn't collide with a Some
containing that string.
Play-JSON 2.8 does introduce KeyWrites
typeclasses to serialize a K
to a JsString
. In the particular case of Option[String]
, though, this may not be of much value as you'd still need to choose a string: if you can be sure that that string will never occur, you could have a KeyWrites
that blows up with an exception if you try to serialize the colliding string. On the KeyReads
side, though, this might lead to some tricky bugs if you get a JSON payload with that string as a key.
Niko's solution for getting a Map[String,V]
is excellent; this answer is going into more detail on the why. You might (if using Play-JSON 2.8) also consider replacing the Option[String]
with a more meaningful constrained type and then defining a KeyWrites
for that type.
Upvotes: 2
Reputation: 800
I have no idea why but the problem here is that the Map is of type Map[Option[String],Seq[KG]]
. If the groupBy is configured in order to retrieve String
s for keys - instead of Option[String]
s - then the serialization is performed successfully and the Json object represents actually a map. So if the code changes to the following:
val theMap: Map[String, Seq[KG]] = ontology.groupBy(_.c match {
case Some(someActualString) => someActualString.getLocalName
case None => "SomethingEmpty"
})
the resulting Json is the following:
{
"someKey": [
{
"d": null,
"b": "person",
"a": "http://someURL",
"c": "http://someOtherURL"
}
]
}
Upvotes: 1