I am using Jackson scala module and I need to serialize/deserialize case object enumerations. The serialization works fine, but when deserializing back the values I get a com.fasterxml.jackson.databind.exc.InvalidDefinitionException. There is a way to make it work? I would like to avoid using "classic" scala enumerations, since I would lose the ability to define enumeration hierarchies.
Code used for the tests:
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
sealed trait Operations { #JsonValue def jsonValue: String }
case object Foo extends Operations { override def jsonValue: String = "Foo" }
case object Bar extends Operations { override def jsonValue: String = "Bar" }
sealed trait RichOperations extends Operations
case object Baz extends RichOperations { override def jsonValue: String = "Baz" }
object JsonUtils extends App {
val jsonMapper = new ObjectMapper() with ScalaObjectMapper
jsonMapper.registerModule(DefaultScalaModule)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
def toJson(value: Any): String = jsonMapper.writeValueAsString(value)
def fromJson[T: Manifest](json: String): T = jsonMapper.readValue[T](json)
println(toJson(Foo)) // "Foo"
println(toJson(Bar)) // "Bar"
println(toJson(Baz)) // "Baz"
println(fromJson[Operations](toJson(Foo))) // throws InvalidDefinitionException
println(fromJson[Operations](toJson(Bar)))
println(fromJson[RichOperations](toJson(Baz)))
}
I solved the problem defining custom deserializers.
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
class OperationsDeserializer extends JsonDeserializer[Operations] {
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Operations = {
p.getValueAsString match {
case Foo.jsonValue => Foo
case Bar.jsonValue => Bar
case value => throw new IllegalArgumentException(s"Undefined deserializer for value: $value")
}
}
}
class RichOperationsDeserializer extends JsonDeserializer[Operations] {
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Operations = {
p.getValueAsString match {
case Foo.jsonValue => Foo
case Bar.jsonValue => Bar
case Baz.jsonValue => Baz
case value => throw new IllegalArgumentException(s"Undefined deserializer for value: $value")
}
}
}
Moreover, since stable identifiers are required in match/case, I changed jsonValue from def to val: this unfortunately requires the declaration of #JsonValue annotation in each case object enumeration.
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
#JsonDeserialize(using = classOf[OperationsDeserializer])
sealed trait Operations { val jsonValue: String }
case object Foo extends Operations { #JsonValue override val jsonValue: String = "Foo" }
case object Bar extends Operations { #JsonValue override val jsonValue: String = "Bar" }
#JsonDeserialize(using = classOf[RichOperationsDeserializer])
sealed trait RichOperations extends Operations
case object Baz extends RichOperations { #JsonValue override val jsonValue: String = "Baz" }
Related
I am writing tests for my case classes and in the following test I have a StackOverflowError:
test("ValuationRequest - Conversion between case object and Json works") {
val caseObject = ValuationRequest(TIME_SERIES_INTRADAY, "", IntraDayIntervals.MIN_5)
val jsonString = caseObject
.asJson
.printWith(Printer.noSpaces)
decode[ValuationRequest](jsonString) must be(Right(caseObject))
}
here's the stacktrace:
An exception or error caused a run to abort.
java.lang.StackOverflowError
at io.circe.syntax.package$EncoderOps$.asJson$extension(package.scala:10)
at eventbus.ValuationRequest$.$anonfun$encodeRequest$1(ValuationRequestCases.scala:14)
....
This is the case code:
import AV_Enums.TimeSeriesFunctions.TIME_SERIES_INTRADAY
import AV_Enums.{IntraDayInterval, IntraDayIntervals, TimeSeriesType}
import cats.syntax.functor._
import io.circe.generic.auto._
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.{Decoder, Encoder}
case class ValuationRequest(function: TimeSeriesType = TIME_SERIES_INTRADAY, symbol: String, interval: IntraDayInterval = IntraDayIntervals.MIN_5)
object ValuationRequest {
implicit val encodeRequest: Encoder[ValuationRequest] = Encoder.instance { case response#ValuationRequest(_, _, _) => response.asJson }
implicit val decodeRequest: Decoder[ValuationRequest] = deriveDecoder[ValuationRequest].widen
}
These are the enums it uses:
sealed abstract class TimeSeriesType(val text: String) extends EnumEntry {}
sealed abstract class IntraDayInterval(val text: String) extends EnumEntry {}
object TimeSeriesFunctions extends Enum[TimeSeriesType] with CirceEnum[TimeSeriesType] {
val values: immutable.IndexedSeq[TimeSeriesType] = findValues
case object TIME_SERIES_INTRADAY extends TimeSeriesType("TIME_SERIES_INTRADAY")
case object TIME_SERIES_DAILY extends TimeSeriesType("TIME_SERIES_DAILY")
case object TIME_SERIES_WEEKLY extends TimeSeriesType("TIME_SERIES_WEEKLY")
case object TIME_SERIES_MONTHLY extends TimeSeriesType("TIME_SERIES_MONTHLY")
}
object IntraDayIntervals extends Enum[IntraDayInterval] with CirceEnum[IntraDayInterval] {
val values: immutable.IndexedSeq[IntraDayInterval] = findValues
case object MIN_1 extends IntraDayInterval("1min")
case object MIN_5 extends IntraDayInterval("5min")
case object MIN_15 extends IntraDayInterval("15min")
case object MIN_30 extends IntraDayInterval("30min")
case object MIN_60 extends IntraDayInterval("60min")
}
I don't understand what is going on with this case, all the others work fine and are implemented the same way. Can anyone help?
Problematic line
Encoder.instance { case response#ValuationRequest(_, _, _) => response.asJson }
.asJson here requires Encoder[ValuationRequest] which is a recursive call. Any reason you cannot use deriveEncoder[ValuationRequest]?
I have created a sealed class for the json field Value under CustomAttribute data class. This field can return String or Array of Strings.
How can we deserialize this sealed class from json?
data class CustomAttribute (
val attributeCode: String,
val value: Value
)
sealed class Value {
class StringArrayValue(val value: List<String>) : Value()
class StringValue(val value: String) : Value()
}
One solution is to use a RuntimeTypeAdapterFactory as per the instructions in this answer
val valueTypeAdapter = RuntimeTypeAdapter.of(Value::class.java)
.registerSubtype(StringArrayValue::class.java)
.registerSubtype(StringValue::class.java)
val gson = GsonBuilder().registerTypeAdapter(valueTypeAdapter).create()
RuntimeTypeAdapter is included in the source code for Gson but not exposed as a Maven artifact.
It is designed to be copy/pasted into your project from here
I created a TypeAdapterFactory implementation specifically to support sealed classes and their subtypes. This works similarly to the RuntimeTypeAdapterFactory (and I used it as a guide to write my class), but will specifically only support sealed types, and will deserialize using object instances of objects with a sealed class supertype (RuntimeTypeAdapterFactory will create a new instance of object types, which breaks equality checks when a single instance is the expectation).
private class SealedTypeAdapterFactory<T : Any> private constructor(
private val baseType: KClass<T>,
private val typeFieldName: String
) : TypeAdapterFactory {
private val subclasses = baseType.sealedSubclasses
private val nameToSubclass = subclasses.associateBy { it.simpleName!! }
init {
if (!baseType.isSealed) throw IllegalArgumentException("$baseType is not a sealed class")
}
override fun <R : Any> create(gson: Gson, type: TypeToken<R>?): TypeAdapter<R>? {
if (type == null || subclasses.isEmpty() || subclasses.none { type.rawType.isAssignableFrom(it.java) }) return null
val elementTypeAdapter = gson.getAdapter(JsonElement::class.java)
val subclassToDelegate: Map<KClass<*>, TypeAdapter<*>> = subclasses.associateWith {
gson.getDelegateAdapter(this, TypeToken.get(it.java))
}
return object : TypeAdapter<R>() {
override fun write(writer: JsonWriter, value: R) {
val srcType = value::class
val label = srcType.simpleName!!
#Suppress("UNCHECKED_CAST") val delegate = subclassToDelegate[srcType] as TypeAdapter<R>
val jsonObject = delegate.toJsonTree(value).asJsonObject
if (jsonObject.has(typeFieldName)) {
throw JsonParseException("cannot serialize $label because it already defines a field named $typeFieldName")
}
val clone = JsonObject()
clone.add(typeFieldName, JsonPrimitive(label))
jsonObject.entrySet().forEach {
clone.add(it.key, it.value)
}
elementTypeAdapter.write(writer, clone)
}
override fun read(reader: JsonReader): R {
val element = elementTypeAdapter.read(reader)
val labelElement = element.asJsonObject.remove(typeFieldName) ?: throw JsonParseException(
"cannot deserialize $baseType because it does not define a field named $typeFieldName"
)
val name = labelElement.asString
val subclass = nameToSubclass[name] ?: throw JsonParseException("cannot find $name subclass of $baseType")
#Suppress("UNCHECKED_CAST")
return (subclass.objectInstance as? R) ?: (subclassToDelegate[subclass]!!.fromJsonTree(element) as R)
}
}
}
companion object {
fun <T : Any> of(clz: KClass<T>) = SealedTypeAdapterFactory(clz, "type")
}
}
Usage:
GsonBuilder().registerTypeAdapter(SealedTypeAdapterFactory.of(Value::class)).create()
I have successfully serialized and de-serialized a sealed class in the past, with a disclaimer of using Jackson, not Gson as my serialization engine.
My sealed class has been defined as:
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, visible = true)
sealed class FlexibleResponseModel
class SnapshotResponse(val collection: List<EntityModel>): FlexibleResponseModel()
class DifferentialResponse(val collection: List<EntityModel>): FlexibleResponseModel()
class EventDrivenResponse(val collection: List<EntityEventModel>): FlexibleResponseModel()
class ErrorResponse(val error: String): FlexibleResponseModel()
With the annotations used, it required no further configuration for the Jackson instance to properly serialize and de-serialize instances of this sealed class granted that both sides of the communication possessed a uniform definition of the sealed class.
While I recognise that JsonTypeInfo is a Jackson-specific annotation, perhaps you might consider switching over from Gson if this feature is a must - or you might be able to find an equivalent configuration for Gson which would also include the class identifier in your serialized data.
In an Akka streams connection I receive JSON objects that looks like this:
{"op":"connection"...}
{"op":"status"...}
..etc
And I have a the following classes setup:
case class ResponseMessage(
op: Option[OpType],
)
case class ConnectionMessage(
a: Option[Int],
b: Option[Int],
) extends ResponseMessage
case class StatusMessage(
c: Option[Int],
d: Option[Int],
) extends ResponseMessage
type OpTypes = OpTypes.Value
object OpTypes extends Enumeration {
val Connection = Value("connection")
val Status = Value("status")
How can I write custom JsonFormat instances so that depending on the
value of op I create the correct type?
So that it can be used as such:
> jsValue.convertTo[ResponseMessage] And the outvalue will be either
> ConnectionMessage or StatusMessage?
Note: it's not best practice to extend case class i.e. ResponseMessage. Google "case class inheritance"...
About your question on JsonFormat, I'd define specific JsonFormats for each subclass, and then define the one for ResponseMessage base on those like:
import spray.json._
abstract class ResponseMessage(val op: Option[OpTypes])
object ResponseMessage {
implicit object jsonFormat extends JsonFormat[ResponseMessage] {
override def read(json: JsValue): ResponseMessage = json match {
case JsObject(fields) =>
fields.get("op") match {
case Some(JsString("connection")) => json.convertTo[ConnectionMessage]
case Some(JsString("status")) => json.convertTo[StatusMessage]
case op => // unknown op
}
case _ => // invalid json
}
override def write(obj: ResponseMessage): JsValue = obj match {
case connection: ConnectionMessage => connection.toJson
case status: StatusMessage => status.toJson
}
}
}
case class ConnectionMessage(
a: Option[Int],
b: Option[Int]
) extends ResponseMessage(Some(OpTypes.Connection))
object ConnectionMessage {
implicit object jsonFormat extends JsonFormat[ConnectionMessage] {
override def read(json: JsValue): ConnectionMessage =
// json -> ConnectionMessage
override def write(obj: ConnectionMessage): JsValue =
// ConnectionMessage -> json
}
}
case class StatusMessage(
c: Option[Int],
d: Option[Int]
) extends ResponseMessage(Some(OpTypes.Status))
object StatusMessage {
implicit object jsonFormat extends JsonFormat[StatusMessage] {
override def read(json: JsValue): StatusMessage =
// json -> StatusMessage
override def write(obj: StatusMessage): JsValue =
// StatusMessage -> json
}
}
type OpTypes = OpTypes.Value
object OpTypes extends Enumeration {
val Connection = Value("connection")
val Status = Value("status")
}
I have an enum class hierarchy like this:
abstract class EnumBase {
def code: String
}
abstract class EnumObject[EnumT <: EnumBase] {...}
I use this in many places. Here is one example:
sealed abstract case class StateCode(code: String) extends EnumBase
object StateCode extends EnumObject[StateCode] {...}
I would like to serialize a case class containing StateCode deeply nested inside into JSON. I have written the following custom serializer to do this:
class EnumSerializer[EnumT <: EnumBase: scala.reflect.ClassTag](enumObject: EnumObject[EnumT])
extends org.json4s.Serializer[EnumT] {
val EnumerationClass = scala.reflect.classTag[EnumT].runtimeClass
def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), EnumT] = {
case (TypeInfo(EnumerationClass, _), json) => json match {
...
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: EnumT => JString(i.code)
case _ => JString("BLAH")
}
}
This isn't working in that the resulting JSON is merely "stateCode":{} when I serialize a case class like this:
MyClass(id = 123,
version = 1,
year = 2016,
input = MyInput(
...
stateCode = StateCode.CO
)
)
Note I am using an Extraction.decompose on the MyInput instance within my custom deserializer for MyClass in the hope my custom serializer will kick in there.
I also tried writing a custom serializer dedicated solely to StateCode just to see if my attempt to be generic is the issue. That didn't help either.
Any ideas on how I can get JSON4S to serialize my enums?
You could try this:
abstract class EnumBase {
def code: String
}
abstract class EnumObject[EnumT <: EnumBase] {}
final case class StateCode(code: String) extends EnumBase
object StateCode extends EnumObject[StateCode] {
val CO = new StateCode("CO")
val CA = new StateCode("CA")
}
case class ClassWithStateCode(id: Int, x: StateCode)
case class MyClass(id: Int, version: Int, year: Int, co: StateCode)
Which doesn't require defining any serializer but this might not be what you are looking for.
Potential issue with what I assume you are doing:
object StateCode extends EnumObject[StateCode] {
val CO = new StateCode("CO") {}
val CA = new StateCode("CA") {}
}
is that you are creating anonymous class which is subtype of StateCode which is case class and you should be very careful extending case classes as they might have wrong equals/hashcode.
This works for non-generic version of serializer but I am afraid this might not be possible to make it generic
abstract class EnumBase {
def code: String
}
abstract class EnumObject[EnumT <: EnumBase] {}
sealed abstract case class StateCode(code: String) extends EnumBase
object StateCode extends EnumObject[StateCode] {
val CO = new StateCode("CO") {}
val CA = new StateCode("CA") {}
}
case class ClassWithStateCode(id: Int, x: StateCode)
case class MyClass(id: Int, version: Int, year: Int, co: StateCode)
object StateCodeSerializer extends org.json4s.Serializer[StateCode] {
override def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), StateCode] = {
case (TypeInfo(EnumerationClass, _), jsonx) => jsonx match {
case i: JString => new StateCode(i.values) {}
}
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: StateCode => JString(i.code)
}
}
implicit val formats2 = DefaultFormats + StateCodeSerializer
val testClass = MyClass(2, 0, 1966, StateCode.CO)
println(write(testClass))
println(read[MyClass](write(testClass)))
Is there an easy way to serialize to json without "tpe" field inside an object?
I need to serialize case classes to json structures and then, send them over the wire (They won't been deserialized in future). I have a specific api, so.. I don't need additional fields.
For class Person illustrated below:
case class Person(Name: String, Age: int, CandyLover: Boolean)
I'd like to see following:
{
"Name": "Paul",
"Age": 23,
"CandyLover": true
}
I would suggest you to take a look at spray-json or argonaut. Or play-json if you are already using Play.
Scala pickling is not really a json library, it was not created to generate JSON for public API, it's not flexible, you can't define JSON protocol first and then provide pickling serialization to match protocol. Pickling is for computer-to-computer serialization where no one is really care about bytes going between apps.
Probably the simplest way to do it is to pass somehow the Hint into JSONPickleBuilder. Quick way to do it is to override builders in JSONPickleFormat by calling hintStaticallyElidedType() method from PickleTools.
Here is kind of code snippet to demonstrate it:
import org.specs2.matcher.JsonMatchers
import org.specs2.mutable.Specification
import org.specs2.specification.Scope
import scala.pickling.Defaults._
import scala.pickling.json.{JSONPickleBuilder, JSONPickleFormat, JsonFormats}
import scala.pickling.{Output, PBuilder, PickleTools, StringOutput}
class PersonJsonFormatsTest extends Specification with JsonMatchers {
trait Context extends Scope with PersonJsonFormats
trait PersonJsonFormats extends JsonFormats {
override implicit val pickleFormat: JSONPickleFormat = new PersonJSONPickleFormat
}
class PersonJSONPickleFormat extends JSONPickleFormat {
override def createBuilder() = new JSONPickleBuilder(this, new StringOutput) with PickleTools {
hintStaticallyElidedType()
}
override def createBuilder(out: Output[String]): PBuilder = new JSONPickleBuilder(this, out) with PickleTools {
hintStaticallyElidedType()
}
}
"Pickle" should {
"Serialize Person without $type field in resulting JSON" in new Context {
case class Person(Name: String, Age: Int, CandyLover: Boolean)
val pickledPersonObject = Person("Paul", 23, CandyLover = true).pickle
println(pickledPersonObject.value)
pickledPersonObject.value must not */("$type" → ".*".r)
}
}
}
The output of println(pickledPersonObject.value) will be as you need:
{
"Name": "Paul",
"Age": 23,
"CandyLover": true
}
This way you will stay aligned with further pickle updates.
P.S. If someone knows more elegant and convenient way to reach the same behaviour - please let us know :-)
For scala-pickling 0.10.x:
import scala.pickling._
import scala.pickling.json.{JSONPickle, JSONPickleBuilder, JSONPickleFormat, JsonFormats}
import scala.pickling.pickler.AllPicklers
object serialization extends JsonFormats with Ops with AllPicklers {
override implicit val pickleFormat: JSONPickleFormat = new JSONPickleFormat {
private def setHints(h: Hintable): Unit = {
h.hintStaticallyElidedType()
h.hintDynamicallyElidedType()
}
override def createBuilder(): JSONPickleBuilder = {
val builder = super.createBuilder()
setHints(builder)
builder
}
override def createBuilder(out: Output[String]): PBuilder = {
val builder = super.createBuilder(out)
setHints(builder)
builder
}
override def createReader(pickle: JSONPickle): PReader = {
val reader = super.createReader(pickle)
setHints(reader)
reader
}
}
}
object SerializationTest extends App {
import serialization._
case class Person(firstName: String, lastName: String)
val pickle: JSONPickle = Person("Evelyn", "Patterson").pickle
val jsonString: String = pickle.value // {"firstName": "Evelyn","lastName": "Patterson"}
val person: Person = jsonString.unpickle[Person]
}
For scala-pickling 0.11.x:
import scala.pickling._
import scala.pickling.json.{JSONPickle, JSONPickleBuilder, JSONPickleFormat}
import scala.pickling.pickler.AllPicklers
object serialization extends AllPicklers {
private final class CustomJSONPickleFormat(tag: FastTypeTag[_]) extends JSONPickleFormat {
private def setHints(h: Hintable) {
h.hintElidedType(tag)
}
override def createBuilder(): JSONPickleBuilder = {
val b = super.createBuilder()
setHints(b)
b
}
override def createBuilder(out: Output[String]): PBuilder = {
val b = super.createBuilder(out)
setHints(b)
b
}
override def createReader(pickle: JSONPickle): PReader = {
val b = super.createReader(pickle)
setHints(b)
b
}
}
implicit val staticOnly = static.StaticOnly // for compile time serialization methods generation
implicit final class EncodeDecodeOps[T](picklee: T) {
def encode(implicit pickler: Pickler[T]): String = {
val pickleFormat = new CustomJSONPickleFormat(pickler.tag)
functions.pickle(picklee)(pickleFormat, pickler).value
}
def decode[A](implicit c: T => String, unpickler: Unpickler[A]): A = {
val pickleFormat = new CustomJSONPickleFormat(unpickler.tag)
functions.unpickle[A](json.JSONPickle(picklee))(unpickler, pickleFormat)
}
}
}
case class Person(firstName: String, lastName: String) {
#transient var x = "test"
}
object SerializationTest extends App {
import serialization._
val jsonString = Person("Lisa", "Daniels").encode
println(jsonString)
val person = jsonString.decode[Person]
println(person)
}