使用 Scala 表示两个 JSON 字段,其中只有一个可以为 null
Using Scala to represent two JSON fields of which only one can be null
假设我的 API returns 看起来像这样的 JSON:
{
"field1": "hey",
"field2": null,
}
我有这个规则,这些字段中只有一个可以同时是null
。在这个例子中,只有 field2
是 null
所以我们没问题。
我可以用以下情况在 Scala 中表示这一点 class:
case class MyFields(
field1: Option[String],
field2: Option[String]
)
并且通过实现一些隐式并让 circe
完成将对象转换为 JSON 的魔法。
object MyFields {
implicit lazy val encoder: Encoder[MyFields] = deriveEncoder[MyFields]
implicit lazy val decoder: Decoder[MyFields] = deriveDecoder[MyFields]
现在,这个策略奏效了。有点。
MyFields(Some("hey"), None)
MyFields(None, Some("hey"))
MyFields(Some("hey"), Some("hey"))
这些都导致 JSON 遵循 规则 。但也可以这样做:
MyFields(None, None)
这将导致 JSON 打破 规则 。
所以这个策略没有充分表达规则。什么是更好的方法?
这是应用程序级数据验证示例,以及范围检查值和其他一致性检查。因此,它并不真正属于原始 JSON 解析,而是需要在单独的验证步骤中完成。
所以我建议有一组 类 直接表示 JSON 数据,然后一组单独的应用程序 类 使用应用程序数据类型而不是 JSON 数据类型。
读取数据时,将JSON读入数据类,检查底层JSON是否有效。然后这些数据 类 通过适当的验证和转换(检查值范围、将字符串更改为枚举等)转换为应用程序 类
这允许在不影响应用程序逻辑的情况下更改数据格式(例如 XML 或数据库)。
将您的数据表示为具有成员 val field1or2 = Either[String, String]
。如果 circe 内置 Codec.codecForEither
不能满足您的确切要求,您可以手动编写编解码器。根据您的描述(field1 和 fiel2 都必须出现在 json 中,一个作为字符串,一个作为 null),类似于
import io.circe.{Decoder, Encoder, HCursor, Json, DecodingFailure}
case class Fields(fields: Either[String, String])
implicit val encodeFields: Encoder[Fields] = new Encoder[Fields] {
final def apply(a: Fields): Json = Json.obj(a.fields match {
case Left(str) => {
"field1" -> Json.fromString(str)
"field2" -> Json.Null
}
case Right(str) => {
"field1" -> Json.Null
"field2" -> Json.fromString(str)
}
})
}
implicit val decodeFields: Decoder[Fields] = new Decoder[Fields] {
final def apply(c: HCursor): Decoder.Result[Fields] ={
val f1 = c.downField("field1").as[Option[String]]
val f2 = c.downField("field1").as[Option[String]]
(f1, f2) match {
case (Right(None), Right(Some(v2))) => Right(Fields(Right(v2)))
case (Right(Some(v1)), Right(None)) => Right(Fields(Left(v1)))
case (Left(failure), _) => Left(failure)
case (_, Left(failure)) => Left(failure)
case (Right(None), Right(None)) => Left(DecodingFailure("Either field1 or field2 must be non-null", Nil))
case (Right(Some(_)), Right(Some(_))) => Left(DecodingFailure("field1 and field2 may not both be non-null", Nil))
}
}
}
(这是基于 and 。)
可以使用
import cats.data.Ior
import io.circe.parser._
import io.circe.syntax._
import io.circe._
case class Fields(fields: Ior[String, String])
implicit val encodeFields: Encoder[Fields] = (a: Fields) =>
a.fields match {
case Ior.Both(v1, v2) => Json.obj(
("field1", Json.fromString(v1)),
("field2", Json.fromString(v2))
)
case Ior.Left(v) => Json.obj(
("field1", Json.fromString(v)),
("field2", Json.Null)
)
case Ior.Right(v) => Json.obj(
("field1", Json.Null),
("field2", Json.fromString(v))
)
}
implicit val decodeFields: Decoder[Fields] = (c: HCursor) => {
val f1 = c.downField("field1").as[Option[String]]
val f2 = c.downField("field2").as[Option[String]]
(f1, f2) match {
case (Right(Some(v1)), Right(Some(v2))) => Right(Fields(Ior.Both(v1, v2)))
case (Right(Some(v1)), Right(None)) => Right(Fields(Ior.Left(v1)))
case (Right(None), Right(Some(v2))) => Right(Fields(Ior.Right(v2)))
case (Left(failure), _) => Left(failure)
case (_, Left(failure)) => Left(failure)
case (Right(None), Right(None)) => Left(DecodingFailure("At least one of field1 or field2 must be non-null", Nil))
}
}
println(Fields(Ior.Right("right")).asJson)
println(Fields(Ior.Left("left")).asJson)
println(Fields(Ior.both("right", "left")).asJson)
println(parse("""{"field1": null, "field2": "right"}""").flatMap(_.as[Fields]))
假设我的 API returns 看起来像这样的 JSON:
{
"field1": "hey",
"field2": null,
}
我有这个规则,这些字段中只有一个可以同时是null
。在这个例子中,只有 field2
是 null
所以我们没问题。
我可以用以下情况在 Scala 中表示这一点 class:
case class MyFields(
field1: Option[String],
field2: Option[String]
)
并且通过实现一些隐式并让 circe
完成将对象转换为 JSON 的魔法。
object MyFields {
implicit lazy val encoder: Encoder[MyFields] = deriveEncoder[MyFields]
implicit lazy val decoder: Decoder[MyFields] = deriveDecoder[MyFields]
现在,这个策略奏效了。有点。
MyFields(Some("hey"), None)
MyFields(None, Some("hey"))
MyFields(Some("hey"), Some("hey"))
这些都导致 JSON 遵循 规则 。但也可以这样做:
MyFields(None, None)
这将导致 JSON 打破 规则 。
所以这个策略没有充分表达规则。什么是更好的方法?
这是应用程序级数据验证示例,以及范围检查值和其他一致性检查。因此,它并不真正属于原始 JSON 解析,而是需要在单独的验证步骤中完成。
所以我建议有一组 类 直接表示 JSON 数据,然后一组单独的应用程序 类 使用应用程序数据类型而不是 JSON 数据类型。
读取数据时,将JSON读入数据类,检查底层JSON是否有效。然后这些数据 类 通过适当的验证和转换(检查值范围、将字符串更改为枚举等)转换为应用程序 类
这允许在不影响应用程序逻辑的情况下更改数据格式(例如 XML 或数据库)。
将您的数据表示为具有成员 val field1or2 = Either[String, String]
。如果 circe 内置 Codec.codecForEither
不能满足您的确切要求,您可以手动编写编解码器。根据您的描述(field1 和 fiel2 都必须出现在 json 中,一个作为字符串,一个作为 null),类似于
import io.circe.{Decoder, Encoder, HCursor, Json, DecodingFailure}
case class Fields(fields: Either[String, String])
implicit val encodeFields: Encoder[Fields] = new Encoder[Fields] {
final def apply(a: Fields): Json = Json.obj(a.fields match {
case Left(str) => {
"field1" -> Json.fromString(str)
"field2" -> Json.Null
}
case Right(str) => {
"field1" -> Json.Null
"field2" -> Json.fromString(str)
}
})
}
implicit val decodeFields: Decoder[Fields] = new Decoder[Fields] {
final def apply(c: HCursor): Decoder.Result[Fields] ={
val f1 = c.downField("field1").as[Option[String]]
val f2 = c.downField("field1").as[Option[String]]
(f1, f2) match {
case (Right(None), Right(Some(v2))) => Right(Fields(Right(v2)))
case (Right(Some(v1)), Right(None)) => Right(Fields(Left(v1)))
case (Left(failure), _) => Left(failure)
case (_, Left(failure)) => Left(failure)
case (Right(None), Right(None)) => Left(DecodingFailure("Either field1 or field2 must be non-null", Nil))
case (Right(Some(_)), Right(Some(_))) => Left(DecodingFailure("field1 and field2 may not both be non-null", Nil))
}
}
}
(这是基于
import cats.data.Ior
import io.circe.parser._
import io.circe.syntax._
import io.circe._
case class Fields(fields: Ior[String, String])
implicit val encodeFields: Encoder[Fields] = (a: Fields) =>
a.fields match {
case Ior.Both(v1, v2) => Json.obj(
("field1", Json.fromString(v1)),
("field2", Json.fromString(v2))
)
case Ior.Left(v) => Json.obj(
("field1", Json.fromString(v)),
("field2", Json.Null)
)
case Ior.Right(v) => Json.obj(
("field1", Json.Null),
("field2", Json.fromString(v))
)
}
implicit val decodeFields: Decoder[Fields] = (c: HCursor) => {
val f1 = c.downField("field1").as[Option[String]]
val f2 = c.downField("field2").as[Option[String]]
(f1, f2) match {
case (Right(Some(v1)), Right(Some(v2))) => Right(Fields(Ior.Both(v1, v2)))
case (Right(Some(v1)), Right(None)) => Right(Fields(Ior.Left(v1)))
case (Right(None), Right(Some(v2))) => Right(Fields(Ior.Right(v2)))
case (Left(failure), _) => Left(failure)
case (_, Left(failure)) => Left(failure)
case (Right(None), Right(None)) => Left(DecodingFailure("At least one of field1 or field2 must be non-null", Nil))
}
}
println(Fields(Ior.Right("right")).asJson)
println(Fields(Ior.Left("left")).asJson)
println(Fields(Ior.both("right", "left")).asJson)
println(parse("""{"field1": null, "field2": "right"}""").flatMap(_.as[Fields]))