如何使用 Circe 半自动地为某种类型的列表派生解码器?
How to derive a decoder semiautomatically for a list of some type with Circe?
我有一个隐含的 class 将服务器的响应解码为 JSON 和后者在正确的情况下 class 以避免重复调用 .as
和 .getOrElse
所有测试:
implicit class RouteTestResultBody(testResult: RouteTestResult) {
def body: String = bodyOf(testResult)
def decodedBody[T](implicit d: Decoder[T]): T =
decode[Json](body)
.fold(err => throw new Exception(s"Body is not a valid JSON: $body"), identity)
.as[T]
.getOrElse(throw new Exception(s"JSON doesn't have the right shape: $body"))
}
当然,这要靠我们通过一个解码器:
import io.circe.generic.semiauto.deriveDecoder
val result: RouteTestResult = ...
result.decodedBody(deriveDecoder[SomeType[AnotherType])
大部分时间都有效,但当响应是列表时失败:
result.dedoceBody(deriveDecoder[List[SomeType]])
// throws "JSON doesn't have the right shape"
如何半自动地为内部具有特定类型的列表派生解码器?
不幸的是,这里的术语太多了,因为我们在两种意义上使用 "deriving":
- 提供实例
List[A]
给出了 A
. 的实例
- 为案例 class 或密封特征层次结构提供实例,为所有成员类型提供实例。
这个问题不是 Circe 或 Scala 特有的。在撰写有关 Circe 的文章时,我通常尽量避免将第一种实例生成称为 "derivation",而将第二种实例称为 "generic derivation" 以强调我们是通过泛型生成实例的代数数据类型的表示。
我们有时使用同一个词来指代两种类型 class 实例生成的事实是一个问题,因为它们在 Scala 中通常是非常不同的机制。在 Circe 中,为 List[A]
提供编码器或解码器实例的东西是 A
类型中的一种方法。比如circe-core中的object Decoder
我们有这样一个方法:
implicit def decodeList[A](implicit decodeA: Decoder[A]): Decoder[List[A]] = ...
因为此方法定义在 Decoder
伴随对象中,如果您在具有隐式 Decoder[A]
的上下文中请求隐式 Decoder[List[A]]
,编译器将找到并使用 decodeList
。您不需要任何导入或额外的定义。例如:
scala> case class Foo(i: Int)
class Foo
scala> import io.circe.Decoder, io.circe.parser
import io.circe.Decoder
import io.circe.parser
scala> implicit val decodeFoo: Decoder[Foo] = Decoder[Int].map(Foo(_))
val decodeFoo: io.circe.Decoder[Foo] = io.circe.Decoder$$anon@6e992c05
scala> parser.decode[List[Foo]]("[1, 2, 3]")
val res0: Either[io.circe.Error,List[Foo]] = Right(List(Foo(1), Foo(2), Foo(3)))
如果我们在这里去除隐含的机制,它看起来像这样:
scala> parser.decode[List[Foo]]("[1, 2, 3]")(Decoder.decodeList(decodeFoo))
val res1: Either[io.circe.Error,List[Foo]] = Right(List(Foo(1), Foo(2), Foo(3)))
请注意,我们可以将第一种推导替换为第二种,它仍然可以编译:
scala> import io.circe.generic.semiauto.deriveDecoder
import io.circe.generic.semiauto.deriveDecoder
scala> parser.decode[List[Foo]]("[1, 2, 3]")(deriveDecoder[List[Foo]])
val res2: Either[io.circe.Error,List[Foo]] = Left(DecodingFailure(CNil, List()))
这可以编译,因为 Scala 的 List
是一种代数数据类型,它具有通用表示,circe-generic 可以为其创建实例。但是,此输入的解码失败,因为此表示不会产生我们期望的编码。我们可以推导出对应的编码器,看看这个编码长什么样子:
scala> import io.circe.Encoder, io.circe.generic.semiauto.deriveEncoder
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder
scala> implicit val encodeFoo: Encoder[Foo] = Encoder[Int].contramap(_.i)
val encodeFoo: io.circe.Encoder[Foo] = io.circe.Encoder$$anon@2717857a
scala> deriveEncoder[List[Foo]].apply(List(Foo(1), Foo(2)))
val res3: io.circe.Json =
{
"::" : [
1,
2
]
}
所以我们实际上看到 List
的 ::
案例 class,这基本上不是我们想要的。
如果您需要显式提供 Decoder[List[Foo]]
,解决方案是使用 Decoder.apply
"summoner" 方法,或者显式调用 Decoder.decodeList
:
scala> Decoder[List[Foo]]
val res4: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon@5d40f590
scala> Decoder.decodeList[Foo]
val res5: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon@2f936a01
scala> Decoder.decodeList(decodeFoo)
val res6: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon@7f525e05
这些都提供完全相同的实例,您应该选择哪个是品味问题。
作为脚注,我考虑过 circe-generic 中的特殊外壳 List
,因此 deriveDecoder[List[X]]
无法编译,因为它几乎从来不是您想要的(但看起来像可能是,尤其是因为我们谈论实例派生的方式令人困惑)。我通常不喜欢这样的特殊情况,但我认为在这种情况下这可能是正确的做法,因为这个问题经常出现。
我有一个隐含的 class 将服务器的响应解码为 JSON 和后者在正确的情况下 class 以避免重复调用 .as
和 .getOrElse
所有测试:
implicit class RouteTestResultBody(testResult: RouteTestResult) {
def body: String = bodyOf(testResult)
def decodedBody[T](implicit d: Decoder[T]): T =
decode[Json](body)
.fold(err => throw new Exception(s"Body is not a valid JSON: $body"), identity)
.as[T]
.getOrElse(throw new Exception(s"JSON doesn't have the right shape: $body"))
}
当然,这要靠我们通过一个解码器:
import io.circe.generic.semiauto.deriveDecoder
val result: RouteTestResult = ...
result.decodedBody(deriveDecoder[SomeType[AnotherType])
大部分时间都有效,但当响应是列表时失败:
result.dedoceBody(deriveDecoder[List[SomeType]])
// throws "JSON doesn't have the right shape"
如何半自动地为内部具有特定类型的列表派生解码器?
不幸的是,这里的术语太多了,因为我们在两种意义上使用 "deriving":
- 提供实例
List[A]
给出了A
. 的实例
- 为案例 class 或密封特征层次结构提供实例,为所有成员类型提供实例。
这个问题不是 Circe 或 Scala 特有的。在撰写有关 Circe 的文章时,我通常尽量避免将第一种实例生成称为 "derivation",而将第二种实例称为 "generic derivation" 以强调我们是通过泛型生成实例的代数数据类型的表示。
我们有时使用同一个词来指代两种类型 class 实例生成的事实是一个问题,因为它们在 Scala 中通常是非常不同的机制。在 Circe 中,为 List[A]
提供编码器或解码器实例的东西是 A
类型中的一种方法。比如circe-core中的object Decoder
我们有这样一个方法:
implicit def decodeList[A](implicit decodeA: Decoder[A]): Decoder[List[A]] = ...
因为此方法定义在 Decoder
伴随对象中,如果您在具有隐式 Decoder[A]
的上下文中请求隐式 Decoder[List[A]]
,编译器将找到并使用 decodeList
。您不需要任何导入或额外的定义。例如:
scala> case class Foo(i: Int)
class Foo
scala> import io.circe.Decoder, io.circe.parser
import io.circe.Decoder
import io.circe.parser
scala> implicit val decodeFoo: Decoder[Foo] = Decoder[Int].map(Foo(_))
val decodeFoo: io.circe.Decoder[Foo] = io.circe.Decoder$$anon@6e992c05
scala> parser.decode[List[Foo]]("[1, 2, 3]")
val res0: Either[io.circe.Error,List[Foo]] = Right(List(Foo(1), Foo(2), Foo(3)))
如果我们在这里去除隐含的机制,它看起来像这样:
scala> parser.decode[List[Foo]]("[1, 2, 3]")(Decoder.decodeList(decodeFoo))
val res1: Either[io.circe.Error,List[Foo]] = Right(List(Foo(1), Foo(2), Foo(3)))
请注意,我们可以将第一种推导替换为第二种,它仍然可以编译:
scala> import io.circe.generic.semiauto.deriveDecoder
import io.circe.generic.semiauto.deriveDecoder
scala> parser.decode[List[Foo]]("[1, 2, 3]")(deriveDecoder[List[Foo]])
val res2: Either[io.circe.Error,List[Foo]] = Left(DecodingFailure(CNil, List()))
这可以编译,因为 Scala 的 List
是一种代数数据类型,它具有通用表示,circe-generic 可以为其创建实例。但是,此输入的解码失败,因为此表示不会产生我们期望的编码。我们可以推导出对应的编码器,看看这个编码长什么样子:
scala> import io.circe.Encoder, io.circe.generic.semiauto.deriveEncoder
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder
scala> implicit val encodeFoo: Encoder[Foo] = Encoder[Int].contramap(_.i)
val encodeFoo: io.circe.Encoder[Foo] = io.circe.Encoder$$anon@2717857a
scala> deriveEncoder[List[Foo]].apply(List(Foo(1), Foo(2)))
val res3: io.circe.Json =
{
"::" : [
1,
2
]
}
所以我们实际上看到 List
的 ::
案例 class,这基本上不是我们想要的。
如果您需要显式提供 Decoder[List[Foo]]
,解决方案是使用 Decoder.apply
"summoner" 方法,或者显式调用 Decoder.decodeList
:
scala> Decoder[List[Foo]]
val res4: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon@5d40f590
scala> Decoder.decodeList[Foo]
val res5: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon@2f936a01
scala> Decoder.decodeList(decodeFoo)
val res6: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon@7f525e05
这些都提供完全相同的实例,您应该选择哪个是品味问题。
作为脚注,我考虑过 circe-generic 中的特殊外壳 List
,因此 deriveDecoder[List[X]]
无法编译,因为它几乎从来不是您想要的(但看起来像可能是,尤其是因为我们谈论实例派生的方式令人困惑)。我通常不喜欢这样的特殊情况,但我认为在这种情况下这可能是正确的做法,因为这个问题经常出现。