Play/Scala: 地图被序列化为一个数组
Play/Scala: Map gets serialized as an array
我有一个 KG class 定义如下(案例 class 和伴随对象):
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"
}
]
]
]
我不知道为什么,但这里的问题是地图的类型是 Map[Option[String],Seq[KG]]
。如果配置 groupBy 是为了检索键的 String
s - 而不是 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"
}
]
}
简短的回答是 Writes[Map[_,_]]
Play JSON 提供的是 Map[String, V]
,因为它们保证可以序列化为 JSON 对象。对于所有其他键类型,Map[K,V]
是 Iterable[(K, V)]
这一事实(如果您对 Lisp 有一些经验,这实际上类似于 "association list":对列表)意味着默认 Writes[Iterable[(K, V)]]
生效。这将每个 (K, V)
序列化为长度为 2 的 JSON 数组,第一个值为 K
,第二个值为 V
;所有这些 JSON 数组都是外部 JSON 数组中的值。
此行为源于这样一个事实,即 JSON 对象中的值可以是任何 JSON 值,但键必须是字符串。在 Option[String]
的特殊情况下,这不起作用,因为没有 JSON 字符串可以用来表示 None
,它不会与 Some
包含那个字符串。
Play-JSON 2.8 确实引入了 KeyWrites
typeclasses 以将 K
序列化为 JsString
。但是,在 Option[String]
的特殊情况下,这可能没有多大价值,因为您仍然需要选择一个字符串:如果您可以确定该字符串 never 发生时,如果您尝试序列化冲突的字符串,您可能会遇到 KeyWrites
异常爆炸。但是,在 KeyReads
方面,如果您获得以该字符串作为键的 JSON 有效载荷,这可能会导致一些棘手的错误。
Niko 获得 Map[String,V]
的解决方案非常好;这个答案将更详细地说明原因。您可能(如果使用 Play-JSON 2.8)还考虑用更有意义的约束类型替换 Option[String]
,然后为该类型定义 KeyWrites
。
我有一个 KG class 定义如下(案例 class 和伴随对象):
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"
}
]
]
]
我不知道为什么,但这里的问题是地图的类型是 Map[Option[String],Seq[KG]]
。如果配置 groupBy 是为了检索键的 String
s - 而不是 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"
}
]
}
简短的回答是 Writes[Map[_,_]]
Play JSON 提供的是 Map[String, V]
,因为它们保证可以序列化为 JSON 对象。对于所有其他键类型,Map[K,V]
是 Iterable[(K, V)]
这一事实(如果您对 Lisp 有一些经验,这实际上类似于 "association list":对列表)意味着默认 Writes[Iterable[(K, V)]]
生效。这将每个 (K, V)
序列化为长度为 2 的 JSON 数组,第一个值为 K
,第二个值为 V
;所有这些 JSON 数组都是外部 JSON 数组中的值。
此行为源于这样一个事实,即 JSON 对象中的值可以是任何 JSON 值,但键必须是字符串。在 Option[String]
的特殊情况下,这不起作用,因为没有 JSON 字符串可以用来表示 None
,它不会与 Some
包含那个字符串。
Play-JSON 2.8 确实引入了 KeyWrites
typeclasses 以将 K
序列化为 JsString
。但是,在 Option[String]
的特殊情况下,这可能没有多大价值,因为您仍然需要选择一个字符串:如果您可以确定该字符串 never 发生时,如果您尝试序列化冲突的字符串,您可能会遇到 KeyWrites
异常爆炸。但是,在 KeyReads
方面,如果您获得以该字符串作为键的 JSON 有效载荷,这可能会导致一些棘手的错误。
Niko 获得 Map[String,V]
的解决方案非常好;这个答案将更详细地说明原因。您可能(如果使用 Play-JSON 2.8)还考虑用更有意义的约束类型替换 Option[String]
,然后为该类型定义 KeyWrites
。