【问题标题】:Argonaut: Generic method to encode/decode array of objectsArgonaut:编码/解码对象数组的通用方法
【发布时间】:2016-05-30 23:58:18
【问题描述】:

我正在尝试实现一个通用模式,使用 Argonaut 为 Akka HTTP REST 服务生成编组器和解组器,同时处理实体和集合级别的请求和响应。我在实现实体级别方面没有任何问题:

case class Foo(foo: String)

object Foo {
  implicit val FooJsonCodec = CodecJson.derive[Foo]

  implicit val EntityEncodeJson = FooJson.Encoder

  implicit val EntityDecodeJson = FooJson.Decoder
}

我在尝试为以下内容提供编码器和解码器时遇到了问题:

[
  { "foo": "1" },
  { "foo": "2" }
]

我已尝试将以下内容添加到我的同伴:

object Foo {
  implicit val FooCollectionJsonCodec = CodecJson.derive[HashSet[Foo]]
}

但是,我收到以下错误:

Error:(33, 90) value jencode0L is not a member of object argonaut.EncodeJson

我看到这种方法确实不存在,但有没有其他通用方法可以生成我的预期结果。我强烈避免使用额外的案例类来描述集合,因为我在我的用例中大量使用反射。

此时,我什至可以使用手动构建的编码器和解码器,但是,我没有找到有关如何使用预期结构构建它的文档。

【问题讨论】:

    标签: json scala akka-http argonaut


    【解决方案1】:

    Argonaut 为 Scala 的不可变列表、集合、流和向量预定义了编码器和解码器。如果您的类型不被明确支持,例如 java.util.HashSet,您可以轻松地为该类型添加 EncodeJson 和 DecodeJson:

    import argonaut._, Argonaut._
    import scala.collection.JavaConverters._
    
    implicit def hashSetEncode[A](
      implicit element: EncodeJson[A]
    ): EncodeJson[java.util.HashSet[A]] =
      EncodeJson(set => EncodeJson.SetEncodeJson[A].apply(set.asScala.toSet))
    
    implicit def hashSetDecode[A](
      implicit element: DecodeJson[A]
    ): DecodeJson[java.util.HashSet[A]] =
      DecodeJson(cursor => DecodeJson.SetDecodeJson[A]
        .apply(cursor)
        .map(set => new java.util.HashSet(set.asJava)))
    
    // Usage:
    
    val set = new java.util.HashSet[Int]
    set.add(1)
    set.add(3)
    val jsonSet = set.asJson // [1, 3]
    jsonSet.jdecode[java.util.HashSet[Int]] // DecodeResult(Right([1, 3]))
    
    case class A(set: java.util.HashSet[Int])
    implicit val codec = CodecJson.derive[A]
    val a = A(set)
    val jsonA = a.asJson // { "set": [1, 3] }
    jsonA.jdecode[A] // DecodeResult(Right(A([1, 3])))
    

    在 Scala 2.12.1 和 Argonaut 6.2-RC2 上检查了示例,但据我所知,它不应该依赖于一些最新的更改。

    这样的方法适用于您想要表示为 JSON 数组的任何线性或无序同质数据结构。此外,这比创建 CodecJson 更可取:后者可以从 JsonEncode 和 JsonDecode 自动推断,但反之则不行。这样,您的 set 将在单独使用或在其他数据类型中使用时进行序列化和反序列化,如示例所示。

    【讨论】:

      【解决方案2】:

      我不使用 Argonaut,但使用 spray-json 和可疑的解决方案可能类似。

      你试过这样的吗?

      implicit def HashSetJsonCodec[T : CodecJson] = CodecJson.derive[Set[T]]
      

      如果它不起作用,我可能会尝试创建更详细的隐式函数,例如

      implicit def SetJsonCodec[T: CodecJson](implicit codec: CodecJson[T]): CodecJson[Set[T]] = {
        CodecJson(
          {
            case value => JArray(value.map(codec.encode).toList)
          },
          c => c.as[JsonArray].flatMap {
            case arr: Json.JsonArray =>
              val items = arr.map(codec.Decoder.decodeJson)
              items.find(_.isError) match {
                case Some(error) => DecodeResult.fail[Set[T]](error.toString(), c.history)
                case None => DecodeResult.ok[Set[T]](items.flatMap(_.value).toSet[T])
              }
          }
        )
      }
      

      PS。我没有对此进行测试,但希望它能引导您走向正确的方向:)

      【讨论】:

      • 为了避免以后给其他人带来麻烦,通用解决方案对我不起作用。我决定转向 spray-json-shapeless,因为它非常自然地支持集合创建。我确实希望第二种方法可行,但是,Argonaut 用于自定义编解码器的 DSL 相当繁琐。
      • @cory-p-oncota 我同意。好吧,我很高兴能帮你转换 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多