【问题标题】:json4s "Can't convert JString(2019-04-28T01:23:45.678Z) to class java.time.Instant"?json4s“无法将 JString(2019-04-28T01:23:45.678Z) 转换为类 java.time.Instant”?
【发布时间】:2019-09-17 16:50:57
【问题描述】:

我正在努力让 json4s 在我的案例类中使用 java.time.Instant 值。 REPL 看起来像这样。

scala> case class TestTime(tag: String, t: java.time.Instant)
defined class TestTime

scala> import org.json4s._
import org.json4s._

scala> import org.json4s.ext.JavaTimeSerializers
import org.json4s.ext.JavaTimeSerializers

scala> import org.json4s.native.JsonMethods._
import org.json4s.native.JsonMethods._

scala> implicit lazy val formats = DefaultFormats ++ JavaTimeSerializers.all
formats: org.json4s.Formats = <lazy>

scala> val parsed = parse("""{"tag": "second","t": "2019-04-28T01:23:45.678Z"}""")
parsed: org.json4s.JValue = JObject(List((tag,JString(second)), (t,JString(2019-04-28T01:23:45.678Z))))

scala> val second = parsed.extract[TestTime]
org.json4s.package$MappingException: No usable value for t
Can't convert JString(2019-04-28T01:23:45.678Z) to class java.time.Instant
  at org.json4s.reflect.package$.fail(package.scala:95)
  at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:569)
  at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:593)
  at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$3.applyOrElse(Extraction.scala:591)
  at scala.PartialFunction.$anonfun$runWith$1$adapted(PartialFunction.scala:145)
  at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
  at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
  at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
  at scala.collection.TraversableLike.collect(TraversableLike.scala:274)
  at scala.collection.TraversableLike.collect$(TraversableLike.scala:272)
  at scala.collection.AbstractTraversable.collect(Traversable.scala:108)
  at org.json4s.Extraction$ClassInstanceBuilder.instantiate(Extraction.scala:591)
  at org.json4s.Extraction$ClassInstanceBuilder.result(Extraction.scala:651)
  at org.json4s.Extraction$.$anonfun$extract$10(Extraction.scala:410)
  at org.json4s.Extraction$.$anonfun$customOrElse$1(Extraction.scala:658)
  at scala.PartialFunction.applyOrElse(PartialFunction.scala:127)
  at scala.PartialFunction.applyOrElse$(PartialFunction.scala:126)
  at scala.PartialFunction$$anon$1.applyOrElse(PartialFunction.scala:257)
  at org.json4s.Extraction$.customOrElse(Extraction.scala:658)
  at org.json4s.Extraction$.extract(Extraction.scala:402)
  at org.json4s.Extraction$.extract(Extraction.scala:40)
  at org.json4s.ExtractableJsonAstNode.extract(ExtractableJsonAstNode.scala:21)
  ... 36 elided
Caused by: org.json4s.package$MappingException: Can't convert JString(2019-04-28T01:23:45.678Z) to class java.time.Instant
  at org.json4s.CustomSerializer$$anonfun$deserialize$2.applyOrElse(Formats.scala:450)
  at org.json4s.CustomSerializer$$anonfun$deserialize$2.applyOrElse(Formats.scala:447)
  at org.json4s.Extraction$.customOrElse(Extraction.scala:658)
  at org.json4s.Extraction$.extract(Extraction.scala:402)
  at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:554)
  ... 56 more

scala> val instant = java.time.Instant.parse("2014-12-03T10:15:30.00Z")
instant: java.time.Instant = 2014-12-03T10:15:30Z

我如何摆脱那个堆栈跟踪并让这个提取工作?

【问题讨论】:

  • 您是否尝试过创建自定义序列化程序?
  • @PhilipWrage 我不应该这样做,这就是 import org.json4s.ext.JavaTimeSerializers++ JavaTimeSerializers.all 正在做的事情,或者应该做的事情。如果我必须做一个“自定义序列化程序”,我会在文档中的哪个位置查找?
  • 我认为@Tim 已经涵盖了以下内容。

标签: scala json4s


【解决方案1】:

查看源代码,Instant 的自定义序列化程序只会解析 Int 值,而不是 String 值:

case object JInstantSerializer extends CustomSerializer[Instant]( format => (
  {
    case JInt(d) => Instant.ofEpochMilli(d.toLong)
    case JNull => null
  },
  {
    case d: Instant => JInt(d.toEpochMilli)
  }
))

您可以添加规则将String 解析为Instant 以解决问题。

case object MyInstantSerialzer extends CustomSerializer[Instant]( format => (
  {
    case JInt(d) => Instant.ofEpochMilli(d.toLong)
    case JString(s) => Instant.parse(s)
    case JNull => null
  },
  {
    case d: Instant => JInt(d.toEpochMilli)
  }
))

implicit lazy val formats = DefaultFormats + MyInstantSerialzer

我个人认为这是有问题的,因为它在读取/写入JSON 时无法保持Instant 的准确性。这样感觉更好:

case object MyInstantSerialzer extends CustomSerializer[Instant]( format => (
  {
    case JString(s) => Instant.parse(s)
    case JNull => null
  },
  {
    case s: Instant => JString(s.toString)
  }
))

this question的回答中还有一个更复杂的解决方案

【讨论】:

  • 我也相信您的“保留Instant 的准确性”变体是更好的解决方案。