如何使用Circe做动态解码?
How to use Circe to do a dynamic decoding?
我的问题有点棘手。我有一个案例 class 看起来像这样
case class Foo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
field3: Boolean,
field4: Boolean
)
但是,我有两种输入方式,一种非常适合class Foo
。另一个缺少 field3
和 field4
的值,看起来像
{id: "Test", name: "Test", field1: true, field2: true}
,我想创建一个适用于这两种情况的 Decoder[Foo]
,如果缺少输入 field3
和 field4
,只需设置默认值 false
。那可能吗?
例如,
(1) 对于输入 {id: "Test", name: "Test", field1: true, field2: true}
,我想将其解码为
Foo("Test, "Test", true, true, false, flase)
(2) 对于输入{id: "Test", name: "Test", field1: true, field2: true, field3: true, field4: false}
,我想把它解码成
Foo("Test, "Test", true, true, true, flase)
我知道最好的解决方案是将 field3
和 field4
设置为 Option[Boolean]
,但是我们有大量代码按照原始设计实现,并且更改数据模型将介绍了很多代码更改。所以只想看看有没有什么make shift的解决办法。
非常感谢!
参数默认值对此不起作用吗?
case class Foo(id: String
,name: String
,field1: Boolean
,field2: Boolean
,field3: Boolean = false
,field4: Boolean = false)
Foo("Jo","Josephine",true,true)
//res0: Foo = Foo(Jo,Josephine,true,true,false,false)
有多种方法可以做到这一点。我假设您不会从头开始构建编解码器,也不会使用您可以从现有资源中获得的资源。
默认参数 + generic-extras
有 circe-generic-extras
包,它允许对自动派生的编解码器进行一些自定义。特别是,它确实允许您使用默认参数作为后备值。
缺点是编译速度稍慢,并且还需要您在范围内有一个隐式 io.circe.generic.extras.Configuration
。
所以,首先你需要隐式配置:
object Configs {
implicit val useDefaultValues = Configuration.default.withDefaults
}
这通常会进入您项目中的一些通用 util 包,因此您可以轻松地重用这些配置。
然后,您在 class 上使用 @ConfiguredJsonCodec
宏注释,或者在它的同伴中使用 extras.semiauto.deriveConfiguredCodec
:
import Configs.useDefaultValues
@ConfiguredJsonCodec
case class Foo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
field3: Boolean = false,
field4: Boolean = false
)
重要的是不要忘记配置导入,并且不要同时导入多个配置。否则你会得到一个无用的错误,比如
could not find Lazy implicit value of type io.circe.generic.extras.codec.ConfiguredAsObjectCodec[Foo]
这足以解码 Foo
,以防缺少默认值的字段:
println {
io.circe.parser.decode[Foo]("""
{
"id": "someid",
"name": "Gordon Freeman",
"field1": false,
"field2": true
}
""")
}
独立的 scastie here.
后备解码器
想法如下:有一个单独的案例 class 描述数据的旧格式,并构建一个解码器以尝试将数据解析为旧格式和新格式。 Circe 解码器有 or
组合器来进行这种尝试。
这里先介绍一下数据的"old"格式,以及升级到新格式的方法:
@JsonCodec(decodeOnly = true)
case class LegacyFoo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
) {
def upgrade: Foo =
Foo(id, name, field1, field2, false, false)
}
使用新格式,您必须手动加入编解码器,因此无法使用宏注释。不过,您可以使用 generic.semiauto.deriveXXX
方法来不必自己列出所有字段:
case class Foo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
field3: Boolean,
field4: Boolean
)
object Foo {
implicit val encoder: Encoder[Foo] = semiauto.deriveEncoder[Foo]
implicit val decoder: Decoder[Foo] =
semiauto.deriveDecoder[Foo] or Decoder[LegacyFoo].map(_.upgrade)
}
对于相同的负载,这也将 "just work":
println {
io.circe.parser.decode[Foo]("""
{
"id": "someid",
"name": "Gordon Freeman",
"field1": false,
"field2": true
}
""")
}
斯卡斯蒂 here.
第一种方法需要额外的库,但样板文件较少。它还将允许调用者提供,例如field4
但不是 field3
- 在第二种方法中, field4
的值将在这种情况下被完全丢弃。
第二个允许处理比 "field added with a default values" 更复杂的更改,例如计算其他几个值或更改集合内部的结构,并且还可以有多个版本,以备日后需要时使用。
哦,你也可以将 LegacyFoo
放入 object Foo
,如果你不想暴露额外的 public 数据类型,则将其设为私有。
我的问题有点棘手。我有一个案例 class 看起来像这样
case class Foo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
field3: Boolean,
field4: Boolean
)
但是,我有两种输入方式,一种非常适合class Foo
。另一个缺少 field3
和 field4
的值,看起来像
{id: "Test", name: "Test", field1: true, field2: true}
,我想创建一个适用于这两种情况的 Decoder[Foo]
,如果缺少输入 field3
和 field4
,只需设置默认值 false
。那可能吗?
例如,
(1) 对于输入 {id: "Test", name: "Test", field1: true, field2: true}
,我想将其解码为
Foo("Test, "Test", true, true, false, flase)
(2) 对于输入{id: "Test", name: "Test", field1: true, field2: true, field3: true, field4: false}
,我想把它解码成
Foo("Test, "Test", true, true, true, flase)
我知道最好的解决方案是将 field3
和 field4
设置为 Option[Boolean]
,但是我们有大量代码按照原始设计实现,并且更改数据模型将介绍了很多代码更改。所以只想看看有没有什么make shift的解决办法。
非常感谢!
参数默认值对此不起作用吗?
case class Foo(id: String
,name: String
,field1: Boolean
,field2: Boolean
,field3: Boolean = false
,field4: Boolean = false)
Foo("Jo","Josephine",true,true)
//res0: Foo = Foo(Jo,Josephine,true,true,false,false)
有多种方法可以做到这一点。我假设您不会从头开始构建编解码器,也不会使用您可以从现有资源中获得的资源。
默认参数 + generic-extras
有 circe-generic-extras
包,它允许对自动派生的编解码器进行一些自定义。特别是,它确实允许您使用默认参数作为后备值。
缺点是编译速度稍慢,并且还需要您在范围内有一个隐式 io.circe.generic.extras.Configuration
。
所以,首先你需要隐式配置:
object Configs {
implicit val useDefaultValues = Configuration.default.withDefaults
}
这通常会进入您项目中的一些通用 util 包,因此您可以轻松地重用这些配置。
然后,您在 class 上使用 @ConfiguredJsonCodec
宏注释,或者在它的同伴中使用 extras.semiauto.deriveConfiguredCodec
:
import Configs.useDefaultValues
@ConfiguredJsonCodec
case class Foo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
field3: Boolean = false,
field4: Boolean = false
)
重要的是不要忘记配置导入,并且不要同时导入多个配置。否则你会得到一个无用的错误,比如
could not find Lazy implicit value of type io.circe.generic.extras.codec.ConfiguredAsObjectCodec[Foo]
这足以解码 Foo
,以防缺少默认值的字段:
println {
io.circe.parser.decode[Foo]("""
{
"id": "someid",
"name": "Gordon Freeman",
"field1": false,
"field2": true
}
""")
}
独立的 scastie here.
后备解码器
想法如下:有一个单独的案例 class 描述数据的旧格式,并构建一个解码器以尝试将数据解析为旧格式和新格式。 Circe 解码器有 or
组合器来进行这种尝试。
这里先介绍一下数据的"old"格式,以及升级到新格式的方法:
@JsonCodec(decodeOnly = true)
case class LegacyFoo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
) {
def upgrade: Foo =
Foo(id, name, field1, field2, false, false)
}
使用新格式,您必须手动加入编解码器,因此无法使用宏注释。不过,您可以使用 generic.semiauto.deriveXXX
方法来不必自己列出所有字段:
case class Foo(
id: String,
name: String,
field1: Boolean,
field2: Boolean,
field3: Boolean,
field4: Boolean
)
object Foo {
implicit val encoder: Encoder[Foo] = semiauto.deriveEncoder[Foo]
implicit val decoder: Decoder[Foo] =
semiauto.deriveDecoder[Foo] or Decoder[LegacyFoo].map(_.upgrade)
}
对于相同的负载,这也将 "just work":
println {
io.circe.parser.decode[Foo]("""
{
"id": "someid",
"name": "Gordon Freeman",
"field1": false,
"field2": true
}
""")
}
斯卡斯蒂 here.
第一种方法需要额外的库,但样板文件较少。它还将允许调用者提供,例如field4
但不是 field3
- 在第二种方法中, field4
的值将在这种情况下被完全丢弃。
第二个允许处理比 "field added with a default values" 更复杂的更改,例如计算其他几个值或更改集合内部的结构,并且还可以有多个版本,以备日后需要时使用。
哦,你也可以将 LegacyFoo
放入 object Foo
,如果你不想暴露额外的 public 数据类型,则将其设为私有。