我想在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中可用)的解决方案持开放态度。
答案 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
Encoder
个implicit 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();
});
特征混合到特定资源类型的伴随对象中,并在那里指明名称。如果这不是一个选项,那么您可能会遇到反射或虚假实例,或两者兼而有之(但同样可能不在此问题的范围内)。