jsoniter-scala
jsoniter-scala copied to clipboard
Traits and JsonCodecMaker
This is perhaps a general misunderstanding on how the JsonCodecMaker
works, but i'm struggling with using the macro when one field is a trait
rather than a case class
. consider this example:
trait OneTypable {
def name: String
}
case class OneType(name: String) extends OneTypable
case class ClassUseOneType(id: String, oneType: OneTypable)
when i use the macro:
implicit JsonValueCodec[ClassUseOneType] = JsonCodecMaker.make[ClassUseOneType](CodecMakerConfig(allowRecursiveTypes = true))
i get a compile time error:
No implicit 'com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[_]' defined for 'OneTypable'.
i'm not entirely sure how to get around this or if i've hit a limitation to what the macro can do.
thank you.
jsoniter-scala derives codecs only for sealed traits.
Generally it is done for security reasons to avoid generation of codecs for unwanted implementations (which can presents in the compilation classpath) of generic traits like Serializable
, Cloneable
, etc.
Also, during code generation the make
macro checks that all non-abstract sub-classes of the sealed trait will have unique discriminator value for type
key (as in example bellow).
Here is how it works for sealed trait:
sealed trait OneTypable {
def name: String
}
case class OneType(name: String) extends OneTypable
case class ClassUseOneType(id: String, oneType: OneTypable)
implicit val classUseOneTypeCodec: JsonValueCodec[ClassUseOneType] =
JsonCodecMaker.make[ClassUseOneType](CodecMakerConfig())
val x = readFromArray("""{"id":"XXX","oneType":{"type":"OneType","name":"YYY"}}""".getBytes("UTF-8"))
val json = writeToArray(ClassUseOneType(id = "XXX", oneType = OneType(name = "YYY")))
println(x)
println(new String(json, "UTF-8"))
Another option is to use wrapping by discriminator JSON object instead of the type
property:
sealed trait OneTypable {
def name: String
}
case class OneType(name: String) extends OneTypable
case class ClassUseOneType(id: String, oneType: OneTypable)
implicit val classUseOneTypeCodec: JsonValueCodec[ClassUseOneType] =
JsonCodecMaker.make[ClassUseOneType](CodecMakerConfig(discriminatorFieldName = None))
val user = readFromArray("""{"id":"XXX","oneType":{"OneType":{"name":"YYY"}}}""".getBytes("UTF-8"))
val json = writeToArray(ClassUseOneType(id = "XXX", oneType = OneType(name = "YYY")))
println(user)
println(new String(json, "UTF-8"))
BTW, if models for serialization to XML and JSON differs too much then you can consider an ability to generate a separate model tree for JSON representation from some JsonSchema.
Also, a mapping between model trees can be defined by the Chimney library to avoid most of boilerplate.
Yet another option would be writing of custom codecs or a custom macro for their generation... Here is example of custom codecs for a simple model: https://github.com/plokhotnyuk/jsoniter-scala/blob/master/jsoniter-scala-core/src/test/scala/com/github/plokhotnyuk/jsoniter_scala/core/UserAPI.scala
@plokhotnyuk ..once again, i appreciate the prompt and thorough response. as you may have probably guessed...just like in my other question, i am using scalaxb to generate the case classes...and it seems that this can be addressed by making the base trait generated as sealed (i opened a ticket or hey maybe i'll attempt to make a PR for that ticket...god help us)