【问题标题】:jackson-module-scala serialize/deserialize Long keys in Map as Stringsjackson-module-scala 将 Map 中的长键序列化/反序列化为字符串
【发布时间】:2018-03-17 06:23:03
【问题描述】:

使用 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(serialized) 语句的输出是:

{"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$1 at (CrazyJacksonTest.scala:17)
Expected :Map(1 -> 1)
Actual   :Map("1" -> 1)

我迷路了! 地图必须是 Map[Long,Long]

由于 Spark 依赖,我必须使用这个版本:

  • Scala 2.11.11
  • jackson-module-scala 2.6.5 并使用 2.9.1 版本进行测试,结果相同。

其他信息:

【问题讨论】:

    标签: json scala serialization jackson circe


    【解决方案1】:

    JSON 只允许键名是字符串。 ECMA-404 The JSON Data Interchange Standard

    对象结构表示为一对花括号标记 围绕零个或多个名称/值对。 名称是一个字符串。

    你是对的,断言问题来自杰克逊。 如您所见,classOf[InnerMap] 实际上映射到 Map<Object, Object> 内部 InnerMap 但您必须将此映射的 typeinfo 提交给 jackson 才能正确反序列化它。它在this documentation 中有解释,根据它你可以使用

    case class InnerMap(@JsonDeserialize(keyAs = classOf[java.lang.Long])
                        map: Map[Long, Long])
    

    【讨论】:

    • 所以问题出在断言上。我认为案例类实现了equals和hashCode。其实为什么“assert(InnerMap(Map(1L->1L)) == InnerMap(Map(1L->1L)))”可以?
    • 拜托,你能看看我的新例子吗?我错过了一些东西。如果案例类定义为 Map[Long,Long],断言如何将内部对象管理为 Map[String, Long]
    • “如何在 Scala 中比较对象是否相等?”的链接是一个不同的案例,因为是比较类而不是案例类。
    • @angelcervera 是的,对不起,我错了,你是对的,它来自杰克逊,而不是来自 equals。更新了我的答案。
    • 官方回应!我现在知道了。我正在寻找 README.md 中链接之后的文档,但所有链接都是错误的。现在检查 wiki 部分。也谢谢你。
    【解决方案2】:

    Scala Jackson 模块不会推断 Map 中键的类型。 正如@Varren 回应的那样,解决方案是用杰克逊注释来注释模型,但是这样:

    • 模型与特定的解析器相关联(模型定义中的杰克逊注释)。
    • 代码不太清楚。

    所以我决定从 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 解析器结合使用,但我没有测试它。

    【讨论】:

      猜你喜欢
      • 2019-10-31
      • 1970-01-01
      • 1970-01-01
      • 2020-05-20
      • 1970-01-01
      • 2017-01-02
      • 1970-01-01
      • 2014-05-19
      • 1970-01-01
      相关资源
      最近更新 更多