简体   繁体   English

Argonaut:编码/解码对象数组的通用方法

[英]Argonaut: Generic method to encode/decode array of objects

I am trying to implement a generic pattern with which to generate marshallers and unmarshallers for an Akka HTTP REST service using Argonaut, handling both entity and collection level requests and responses. 我正在尝试实现一种通用模式,使用该模式可以使用Argonaut为Akka HTTP REST服务生成编组器和解组器,同时处理实体和集合级别的请求和响应。 I have no issues in implementing the entity level as such: 在实现这样的实体级别上,我没有任何问题:

case class Foo(foo: String)

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

  implicit val EntityEncodeJson = FooJson.Encoder

  implicit val EntityDecodeJson = FooJson.Decoder
}

I am running into issues attempting to provide encoders and decoders for the following: 我在尝试为以下内容提供编码器和解码器时遇到问题:

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

I have attempted adding the following to my companion: 我试图将以下内容添加到我的同伴中:

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

However, I am receiving the following error: 但是,我收到以下错误:

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

I see this method truly does not exist but is there any other generic method to generate my expected result. 我看到这种方法确实不存在,但是还有其他通用方法可以产生预期的结果。 I'm strongly avoiding using an additional case class to describe the collection since I am using reflection heavily in my use case. 我极力避免使用其他案例类来描述集合,因为我在用例中大量使用了反射。

At this point, I'd even be fine with a manually constructed Encoder and Decoder, however, I've found no documentation on how to construct it with the expected structure. 在这一点上,我什至可以使用手动构造的Encoder和Decoder很好,但是,我还没有找到有关如何使用预期结构构造它的文档。

Argonaut have predefined encoders and decoders for Scala's immutable lists, sets, streams and vectors. Argonaut具有针对Scala的不可变列表,集合,流和向量的预定义编码器和解码器。 If your type is not supported explicitly, as in the case of java.util.HashSet, you can easily add EncodeJson and DecodeJson for the type: 如果不明确支持您的类型(如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])))

Sample is checked on Scala 2.12.1 and Argonaut 6.2-RC2, but as far as I know it shouldn't depend on some latest changes. 在Scala 2.12.1和Argonaut 6.2-RC2上检查了示例,但据我所知它不应该依赖于某些最新更改。

Approach like this works with any linear or unordered homogenous data structure that you want to represent as JSON array. 这样的方法适用于您要表示为JSON数组的任何线性或无序同构数据结构。 Also, this is preferable to creating a CodecJson: latter can be inferred automatically from JsonEncode and JsonDecode, but not vice versa. 同样,这比创建CodecJson更可取:可以从JsonEncode和JsonDecode自动推断出后者,反之则不然。 This way, your set will serialize and deserialize both when used independently or within other data type, as shown in example. 这样,您的集合将在独立使用或在其他数据类型内使用时进行序列化和反序列化,如示例所示。

I don't use Argonaut but use spray-json and suspect solution can be similar. 我不使用Argonaut,但使用spray-json,怀疑解决方案可能相似。

Have you tried something like this ? 你尝试过这样的事情吗?

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

if it doesn't work I'd probably try creating more verbose implicit function like 如果不起作用,我可能会尝试创建更多详细的隐式函数,例如

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. PS。 I didn't test this but hopefully it leads you to the right direction :) 我没有对此进行测试,但希望它能将您引向正确的方向:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM