jackson-module-scala serialize/deserialize 映射中的长键作为字符串
jackson-module-scala serialize/deserialize Long keys in Map as Strings
使用 jackson-module-Scala,我尝试使用 Long as key 序列化和反序列化带有内部 Map 的对象,但是 Jackson 将密钥序列化为 String 并且没有如果忽略 Class 中定义的类型,则不要将其反序列化为 Long。
是BUG吗?我做错了什么吗?
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
case class InnerMap(map: Map[Long, Long])
object CrazyJackson {
def main(args: Array[String]): Unit = {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val innerMap = InnerMap(Map(1L->1L))
val serialized = mapper.writeValueAsString(innerMap)
val newObj = mapper.readValue(serialized, classOf[InnerMap])
println(serialized) // Why the key is serialized as a String?
println(innerMap)
println(newObj)
assert(newObj == innerMap)
}
}
断言失败,println(序列化)语句的输出为:
{"map":{"1":1}}
很奇怪打印newObj和innerMap是一样的:
InnerMap(Map(1 -> 1))
InnerMap(Map(1 -> 1))
正如@Varren 所说,问题确实出在断言中。但是:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import org.scalatest.FunSuite
class CrazyJacksonTest extends FunSuite {
test("test json comparision") {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val innerMap = InnerMap(Map(1L->1L))
val serialized = mapper.writeValueAsString(innerMap)
val newObj = mapper.readValue(serialized, classOf[InnerMap])
assert(newObj.map == innerMap.map)
}
}
断言结果:
Map("1" -> 1) did not equal Map(1 -> 1)
ScalaTestFailureLocation: CrazyJacksonTest$$anonfun at (CrazyJacksonTest.scala:17)
Expected :Map(1 -> 1)
Actual :Map("1" -> 1)
我迷路了! 地图必须是地图[Long,Long]!
由于 Spark 依赖项,我必须使用此版本:
- Scala 2.11.11
- jackson-module-scala 2.6.5 并使用版本 2.9.1 进行测试,结果相同。
其他信息:
JSON 只允许键名是字符串。 ECMA-404 The JSON Data Interchange Standard
An object structure is represented as a pair of curly bracket tokens
surrounding zero or more name/value pairs. A name is a string.
你是对的,断言问题来自杰克逊。
如您所见,classOf[InnerMap]
实际上映射到 InnerMap
内部的 Map<Object, Object>
,但您必须将此映射的类型信息提交给 jackson 才能正确反序列化。它在 this documentation 中有解释,根据它你可以只使用
case class InnerMap(@JsonDeserialize(keyAs = classOf[java.lang.Long])
map: Map[Long, Long])
Scala Jackson 模块不推断 Map 中键的类型。
正如@Varren 回应的那样,解决方案是用 Jackson 注释对模型进行注释,但是在这种方式下:
- 模型绑定到特定的解析器(模型定义中的 Jackson 注释)。
- 代码不太清楚。
所以我决定从 Jackson 转移到 Circe 并 删除注释 以保持代码干净。
这是一个证明它正确解析和反解析的测试:
test("test json circe comparision") {
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
val innerMap = InnerMap(Map(1L -> 1L))
val jsonStr = innerMap.asJson.noSpaces
decode[InnerMap](jsonStr) match {
case Right(innerMap2) => assert(innerMap2 == innerMap)
case Left(error) => fail(error)
}
}
这并不意味着这是适合所有人的最佳解决方案。
Circe有一个插件可以结合Jackson解析器使用,但我没有测试。
使用 jackson-module-Scala,我尝试使用 Long as key 序列化和反序列化带有内部 Map 的对象,但是 Jackson 将密钥序列化为 String 并且没有如果忽略 Class 中定义的类型,则不要将其反序列化为 Long。 是BUG吗?我做错了什么吗?
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
case class InnerMap(map: Map[Long, Long])
object CrazyJackson {
def main(args: Array[String]): Unit = {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val innerMap = InnerMap(Map(1L->1L))
val serialized = mapper.writeValueAsString(innerMap)
val newObj = mapper.readValue(serialized, classOf[InnerMap])
println(serialized) // Why the key is serialized as a String?
println(innerMap)
println(newObj)
assert(newObj == innerMap)
}
}
断言失败,println(序列化)语句的输出为:
{"map":{"1":1}}
很奇怪打印newObj和innerMap是一样的:
InnerMap(Map(1 -> 1))
InnerMap(Map(1 -> 1))
正如@Varren 所说,问题确实出在断言中。但是:
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import org.scalatest.FunSuite
class CrazyJacksonTest extends FunSuite {
test("test json comparision") {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val innerMap = InnerMap(Map(1L->1L))
val serialized = mapper.writeValueAsString(innerMap)
val newObj = mapper.readValue(serialized, classOf[InnerMap])
assert(newObj.map == innerMap.map)
}
}
断言结果:
Map("1" -> 1) did not equal Map(1 -> 1)
ScalaTestFailureLocation: CrazyJacksonTest$$anonfun at (CrazyJacksonTest.scala:17)
Expected :Map(1 -> 1)
Actual :Map("1" -> 1)
我迷路了! 地图必须是地图[Long,Long]!
由于 Spark 依赖项,我必须使用此版本:
- Scala 2.11.11
- jackson-module-scala 2.6.5 并使用版本 2.9.1 进行测试,结果相同。
其他信息:
JSON 只允许键名是字符串。 ECMA-404 The JSON Data Interchange Standard
An object structure is represented as a pair of curly bracket tokens surrounding zero or more name/value pairs. A name is a string.
你是对的,断言问题来自杰克逊。classOf[InnerMap]
实际上映射到 InnerMap
内部的 Map<Object, Object>
,但您必须将此映射的类型信息提交给 jackson 才能正确反序列化。它在 this documentation 中有解释,根据它你可以只使用
case class InnerMap(@JsonDeserialize(keyAs = classOf[java.lang.Long])
map: Map[Long, Long])
Scala Jackson 模块不推断 Map 中键的类型。 正如@Varren 回应的那样,解决方案是用 Jackson 注释对模型进行注释,但是在这种方式下:
- 模型绑定到特定的解析器(模型定义中的 Jackson 注释)。
- 代码不太清楚。
所以我决定从 Jackson 转移到 Circe 并 删除注释 以保持代码干净。 这是一个证明它正确解析和反解析的测试:
test("test json circe comparision") {
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
val innerMap = InnerMap(Map(1L -> 1L))
val jsonStr = innerMap.asJson.noSpaces
decode[InnerMap](jsonStr) match {
case Right(innerMap2) => assert(innerMap2 == innerMap)
case Left(error) => fail(error)
}
}
这并不意味着这是适合所有人的最佳解决方案。 Circe有一个插件可以结合Jackson解析器使用,但我没有测试。