Play/Scala:Map被序列化为数组



我有一个这样的KG类(case类和配套对象(:

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

这是我的 sbt 控制台在尝试弄清楚发生了什么时的输出

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

虽然initial是一个 Map,但它被序列化为一个数组。这是为什么呢?我是否错误地配置了任何序列化隐式?

[
[
"another-url-this-is", 
[
{
"d": null, 
"b": "test", 
"a": "http://some-url-this-is", 
"c": "http://another-url-this-is"
}
]
]
]

简短的回答是,Play JSON 提供的唯一Writes[Map[_,_]]是用于Map[String, V],因为它们保证可以序列化为 JSON 对象。 对于所有其他键类型,Map[K,V]Iterable[(K, V)]的事实(如果你对 Lisp 有一些经验,这实际上就像一个"关联列表":一个对的列表(意味着默认Writes[Iterable[(K, V)]]生效。 这会将每个(K, V)序列化为长度为 2 的 JSON 数组,第一个值是K,第二个值是V;然后,所有这些 JSON 数组都是外部 JSON 数组中的值。

此行为源于以下事实:虽然 JSON 对象中的值可以是任何 JSON 值,但键必须是字符串。 在Option[String]的特殊情况下,这是行不通的,因为没有JSON字符串可以用来表示不会与包含该字符串的Some发生冲突的None

Play-JSON 2.8确实引入了KeyWrites类型类来将K序列化为JsString。 但是,在Option[String]的特殊情况下,这可能没有多大价值,因为您仍然需要选择一个字符串:如果您可以确定该字符串永远不会发生,那么如果您尝试序列化冲突字符串,您可能会有一个异常KeyWrites。 但是,从KeyReads方面来看,如果您获得使用该字符串作为键的 JSON 有效负载,这可能会导致一些棘手的错误。

Niko获得Map[String,V]的解决方案非常好;这个答案将更详细地说明原因。 您也可以(如果使用 Play-JSON 2.8(考虑将Option[String]替换为更有意义的约束类型,然后为该类型定义KeyWrites

我不知道为什么,但这里的问题是地图是Map[Option[String],Seq[KG]]型。如果 groupBy 配置为检索键的Strings(而不是Option[String]s(,则序列化将成功执行,并且 Json 对象实际上表示一个映射。因此,如果代码更改为以下内容:

val theMap: Map[String, Seq[KG]] = ontology.groupBy(_.c match {
case Some(someActualString) => someActualString.getLocalName
case None => "SomethingEmpty"
})

生成的 Json 如下所示:

{
"someKey": [
{
"d": null, 
"b": "person", 
"a": "http://someURL", 
"c": "http://someOtherURL"
}
]
}

最新更新