json - 反序列化 JSON 区分缺失值和空值

标签 json scala play-json

我有一个解析 JSON 对象的需求,使用 play-json 并区分缺失值、字符串值和空值。

例如,我可能想反序列化为以下案例类:

case class MyCaseClass(
  a: Option[Option[String]]
)

其中“a”的值表示:

  • 无 - 缺少“a” - 正常的 play-json 行为
  • Some(Some(String)) - “a”有一个字符串值
  • Some(None) - “a”有一个空值

所以预期行为的例子是:

{}

should deserialize to myCaseClass(None)

{
  "a": null
} 

should deserialize as myCaseClass(Some(None))

{
  "a": "a"
}

should deserialize as myCaseClass(Some(Some("a"))

我尝试编写自定义格式化程序,但 formatNullable 和 formatNullableWithDefault 方法不区分缺失值和空值,因此我在下面编写的代码无法生成 Some(None) 结果

object myCaseClass {
  implicit val aFormat: Format[Option[String]] = new Format[Option[String]] {
    override def reads(json: JsValue): JsResult[Option[String]] = {
      json match {
        case JsNull => JsSuccess(None) // this is never reached
        case JsString(value) => JsSuccess(Some(value))
        case _ => throw new RuntimeException("unexpected type")
      }
    }
    override def writes(codename: Option[String]): JsValue = {
      codename match {
        case None => JsNull
        case Some(value) =>  JsString(value)
      }
    }
  }

  implicit val format = (
      (__ \ "a").formatNullableWithDefault[Option[String]](None)
  )(MyCaseClass.apply, unlift(MyCaseClass.unapply))
}

我是不是漏掉了什么技巧?我该怎么办?我非常愿意以 Option[Option[Sting]] 以外的其他方式对最终值进行编码,例如某种封装此的案例类:

case class MyContainer(newValue: Option[String], wasProvided: Boolean)

最佳答案

我最近找到了一个合理的方法来做到这一点。我正在使用 Play 2.6.11,但我猜测这种方法会转移到其他最新版本。

以下代码片段将三个扩展方法添加到 JsPath,以读取/写入/格式化 Option[Option[A]] 类型的字段。在每种情况下,缺少的字段映射到 Nonenull 映射到 Some(None),非空值映射到 Some(Some(a)) 作为原始发帖者的要求:

import play.api.libs.json._

object tristate {
  implicit class TriStateNullableJsPathOps(path: JsPath) {
    def readTriStateNullable[A: Reads]: Reads[Option[Option[A]]] =
      Reads[Option[Option[A]]] { value =>
        value.validate[JsObject].flatMap { obj =>
          path.asSingleJsResult(obj) match {
            case JsError(_)           => JsSuccess(Option.empty[Option[A]])
            case JsSuccess(JsNull, _) => JsSuccess(Option(Option.empty[A]))
            case JsSuccess(json, _)   => json.validate[A]
                                             .repath(path)
                                             .map(a => Option(Option(a)))
          }
        }
      }

    def writeTriStateNullable[A: Writes]: OWrites[Option[Option[A]]] =
      path.writeNullable(Writes.optionWithNull[A])

    def formatTriStateNullable[A: Format]: OFormat[Option[Option[A]]] =
      OFormat(readTriStateNullable[A], writeTriStateNullable[A])
  }
}

与此线程中之前的建议一样,此方法要求您使用应用性 DSL 完整地写出 JSON 格式。不幸的是,它与 Json.format 宏不兼容,但它可以让您接近您想要的。这是一个用例:

import play.api.libs.json._
import play.api.libs.functional.syntax._
import tristate._

case class Coord(col: Option[Option[String]], row: Option[Option[Int]])

implicit val format: OFormat[Coord] = (
  (__ \ "col").formatTriStateNullable[String] ~
  (__ \ "row").formatTriStateNullable[Int]
)(Coord.apply, unlift(Coord.unapply))

一些写法示例:

format.writes(Coord(None, None))
// => {}

format.writes(Coord(Some(None), Some(None)))
// => { "col": null, "row": null }

format.writes(Coord(Some(Some("A")), Some(Some(1))))
// => { "col": "A", "row": 1 }

还有一些阅读的例子:

Json.obj().as[Coord]
// => Coord(None, None)

Json.obj(
  "col" -> JsNull, 
  "row" -> JsNull
).as[Coord]
// => Coord(Some(None), Some(None))

Json.obj(
  "col" -> "A", 
  "row" -> 1
).as[Coord]
// => Coord(Some(Some("A")), Some(Some(1)))

作为读者的额外练习,您可以将它与一些无形的东西结合起来自动派生编解码器并用不同的单行代码替换 Json.format 宏(尽管需要更长的时间编译)。

关于json - 反序列化 JSON 区分缺失值和空值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48510822/

相关文章:

json - 没有 play.api.libs.json.Format 的实例可用于 scala.Predef.Map[java.lang.String, scala.Option[scala.Double]]

json - 将 List[JsResult[A]] 排序为 JsResult[List[A]]

python - 使用 Python 进行简单的 JSON 编码

javascript - 如何从Json输出中获取特定信息

javascript - 从嵌套的 javascript 对象中删除属性的最佳方法是什么?

scala - 如何直接使用scala.collection.immutable.Map的API sum?

scala - 如何修复 Scala 中的这种类型不匹配错误?

c# - 循环动态 JSON 以获取所有节点 C#

javascript - Scala 方式编写 JavaScript 的 'foo["bar"]'

scala - 我如何在 play.api.libs.json.JsValue 和 org.json4s.JValue 之间转换