如何定义动态基础json对象?

时间:2017-03-15 17:14:46

标签: json scala

我想在Scala中设计一个可以生成以下json的基本特征/类:

trait GenericResource {
  val singularName: String
  val pluralName: String
}

我会在案例类中继承这个特性:

case class Product(name: String) extends GenericResource {
  override val singularName = "product"
  override val pluralName = "products"
}
val car = Product("car")
val jsonString = serialize(car)

输出应如下所示:{"product":{"name":"car"}}

Seq[Product]应生成{"products":[{"name":"car"},{"name":"truck"}]}等...

我正在努力通过适当的抽象来实现这一目标。我对使用任何JSON库(在Scala中可用)的解决方案持开放态度。

1 个答案:

答案 0 :(得分:2)

这里是关于我可以想到的最简单的方法,一般用circe来做奇异的部分:

import io.circe.{ Decoder, Encoder, Json }
import io.circe.generic.encoding.DerivedObjectEncoder

trait GenericResource {
  val singularName: String
  val pluralName: String
}

object GenericResource {
  implicit def encodeResource[A <: GenericResource](implicit
    derived: DerivedObjectEncoder[A]
  ): Encoder[A] = Encoder.instance { a =>
    Json.obj(a.singularName -> derived(a))
  }
}

然后,如果你有一些案例类扩展GenericResource,就像这样:

case class Product(name: String) extends GenericResource {
  val singularName = "product"
  val pluralName = "products"
}

您可以这样做(假设案例类的所有成员都是可编码的):

scala> import io.circe.syntax._
import io.circe.syntax._

scala> Product("car").asJson.noSpaces
res0: String = {"product":{"name":"car"}}

没有样板,没有额外的进口等等。

Seq案例有点棘手,因为circe会自动为Seq[A] A Encoderimplicit def encodeResources[A <: GenericResource](implicit derived: DerivedObjectEncoder[A] ): Encoder[Seq[A]] = Encoder.instance { case values @ (head +: _) => Json.obj(head.pluralName -> Encoder.encodeList(derived)(values.toList)) case Nil => Json.obj() } 编码器提供scala> Seq(Product("car"), Product("truck")).asJson.noSpaces res1: String = {"products":[{"name":"car"},{"name":"truck"}]} 编码器,但它不会你想要什么 - 它只是对项目进行编码并将它们粘贴在JSON数组中。你可以这样写:

Seq[A]

并像这样使用它:

encodeResources

但你不能只是把它放在伴侣对象中并希望一切都能正常工作 - 你必须把它放在某处并在需要时导入它(否则它具有与默认{{1}相同的优先级。实例)。

Seq实现的另一个问题是,如果scala> Seq.empty[Product].asJson.noSpaces res2: String = {} 为空,它只返回一个空对象:

GenericResourceCompanion

这是因为复数名称附加到实例级别的资源,如果您没有实例,则无法获取它(缺少反射)。你当然可以通过将null传递给构造函数或者其他东西来构成一个假实例,但这似乎超出了这个问题的范围。

如果您需要解码已编码的JSON,则此问题(附加到实例的资源名称)也会出现问题。如果是这种情况,我建议考虑一种稍微不同的方法,你可以将PerformSelectedOperationCommand = ReactiveCommand.CreateFromObservable(PerformOperationObservable.SubscribeOn(RxApp.TaskPoolScheduler), // Move subscription to task pool this.WhenAnyValue(x => x.SelectedPath, x => x.TotalFilesSelected, (x, y) => x != null && y > 0)); // listen to the messages and append to output PerformSelectedOperationCommand .ObserveOnDispather() // Return to UI thread .Subscribe(s => { sb.AppendLine(s); ProgressWindowOutput = sb.ToString(); }); 特征混合到特定资源类型的伴随对象中,并在那里指明名称。如果这不是一个选项,那么您可能会遇到反射或虚假实例,或两者兼而有之(但同样可能不在此问题的范围内)。