json - 如何将JSON空值解码为空集合

标签 json scala circe

假设我有一个这样的Scala案例类:

case class Stuff(id: String, values: List[String])

而且我希望能够将以下JSON值解码到其中:
{ "id": "foo", "values": ["a", "b", "c"] }
{ "id": "bar", "values": [] }
{ "id": "qux", "values": null }

Circe中,您从通用派生获得的解码器适用于前两种情况,但不适用于第三种情况:
scala> decode[Stuff]("""{ "id": "foo", "values": ["a", "b", "c"] }""")
res0: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(a, b, c)))

scala> decode[Stuff]("""{ "id": "foo", "values": [] }""")
res1: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List()))

scala> decode[Stuff]("""{ "id": "foo", "values": null }""")
res2: Either[io.circe.Error,Stuff] = Left(DecodingFailure(C[A], List(DownField(values))))

我该如何使我的解码器在这种情况下工作,最好不必处理完全手写定义的样板。

最佳答案

用游标进行预处理

解决此问题的最直接方法是使用半自动派生,并使用prepare预处理JSON输入。例如:

import io.circe.{Decoder, Json}, io.circe.generic.semiauto._, io.circe.jawn.decode

case class Stuff(id: String, values: List[String])

def nullToNil(value: Json): Json = if (value.isNull) Json.arr() else value

implicit val decodeStuff: Decoder[Stuff] = deriveDecoder[Stuff].prepare(
  _.downField("values").withFocus(nullToNil).up
)

然后:
scala> decode[Stuff]("""{ "id": "foo", "values": null }""")
res0: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List()))

它比简单地使用deriveDecoder更为冗长,但是它仍然可以避免写出所有case类成员的样板,并且如果只有少数case类包含需要这种处理的成员,那还不错。

处理遗漏的字段

如果您还想处理该字段完全丢失的情况,则需要执行额外的步骤:
implicit val decodeStuff: Decoder[Stuff] = deriveDecoder[Stuff].prepare { c =>
  val field = c.downField("values")

  if (field.failed) {
    c.withFocus(_.mapObject(_.add("values", Json.arr())))
  } else field.withFocus(nullToNil).up
}

然后:
scala> decode[Stuff]("""{ "id": "foo", "values": null }""")
res1: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List()))

scala> decode[Stuff]("""{ "id": "foo" }""")
res2: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List()))

这种方法实质上使您的解码器的行为与成员类型为Option[List[String]]时的行为完全相同。

捆绑这个

您可以使用如下所示的辅助方法使此操作更加方便:
import io.circe.{ACursor, Decoder, Json}
import io.circe.generic.decoding.DerivedDecoder

def deriveCustomDecoder[A: DerivedDecoder](fieldsToFix: String*): Decoder[A] = {
  val preparation = fieldsToFix.foldLeft[ACursor => ACursor](identity) {
    case (acc, fieldName) =>
      acc.andThen { c =>
        val field = c.downField(fieldName)

        if (field.failed) {
          c.withFocus(_.mapObject(_.add(fieldName, Json.arr())))
        } else field.withFocus(nullToNil).up
      }
  }

  implicitly[DerivedDecoder[A]].prepare(preparation)
}

您可以这样使用:
case class Stuff(id: String, values: Seq[String], other: Seq[Boolean])

implicit val decodeStuff: Decoder[Stuff] = deriveCustomDecoder("values", "other")

然后:
scala> decode[Stuff]("""{ "id": "foo", "values": null }""")
res1: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List()))

scala> decode[Stuff]("""{ "id": "foo" }""")
res2: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List()))

scala> decode[Stuff]("""{ "id": "foo", "other": [true] }""")
res3: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List(true)))

scala> decode[Stuff]("""{ "id": "foo", "other": null }""")
res4: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List()))

这使您有95%的返回可以轻松使用半自动推导,但是如果还不够的话……

核选择

如果您拥有很多需要进行这种处理的成员的案例类,而又不想全部修改它们,则可以采用更极端的方法来为各处的Decoder修改Seq的行为:
import io.circe.Decoder

implicit def decodeSeq[A: Decoder]: Decoder[Seq[A]] =
  Decoder.decodeOption(Decoder.decodeSeq[A]).map(_.toSeq.flatten)

然后,如果您有这样的案例类:
case class Stuff(id: String, values: Seq[String], other: Seq[Boolean])

派生的解码器将自动执行您想要的操作:
scala> import io.circe.generic.auto._, io.circe.jawn.decode
import io.circe.generic.auto._
import io.circe.jawn.decode

scala> decode[Stuff]("""{ "id": "foo", "values": null }""")
res0: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List()))

scala> decode[Stuff]("""{ "id": "foo" }""")
res1: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List()))

scala> decode[Stuff]("""{ "id": "foo", "other": [true] }""")
res2: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List(true)))

scala> decode[Stuff]("""{ "id": "foo", "other": null }""")
res3: Either[io.circe.Error,Stuff] = Right(Stuff(foo,List(),List()))

但是,我强烈建议您坚持使用上述更明确的版本,因为依赖于将Decoder的行为更改为Seq会使您处于必须谨慎对待范围内隐式对象的位置。

这个问题经常出现,我们可以为在将来的Circe版本中需要将null映射到空集合的人提供特定的支持。

关于json - 如何将JSON空值解码为空集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57919641/

相关文章:

scala - 如何使用多个可变参数方法(类型橡皮擦问题)在 Scala 中实现 Java 接口(interface)?

scala - 从类型中获取类型标签?

scala - Kafka 生产者从本地 Linux 文件夹中读取数据

json - Scala Circe。编码器类型 任意

ios - 当我尝试构建项目时出现文件重复错误

javascript - `JSON.stringify` 省略数组中的自定义键

c# - 使用 JSON.NET 在 C# 中进行无痛 JSON 反序列化

python - 解析包含JSON和文本结构的txt文件中的JSON结构

scala - 如何使用 Circe 创建选项类型的自定义编码?

scala - sbt 中的 Jackson 版本 : 2. 7.1 不兼容?