JSON deserialization using Jackson in Scala - json

I'm new to Jackson. I am encountering the following error when using Jackson to deserialize a JSON string if I don't explicitly specify a class type during deserialization:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate abstract type [simple type, class scala.runtime.Nothing$] (need to add/enable type information?)
Is there a way to serialize / deserialize JSON in Jackson without having to specify the class type?
Here is my test code:
import java.lang.reflect.{Type, ParameterizedType}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.`type`.TypeReference;
import org.junit._
import org.junit.Assert._
case class Person (name:String)
case class Address (city:String, state:String)
class JSONTest {
#Test
def jsonTest () = {
val p = new Person ("Bob")
val json = JacksonWrapper.serialize(p)
println ("json= " + json)
val obj = JacksonWrapper.deserialize[Person](json)
println ("obj = " + obj)
// fails since class type isn't explictly specified.
// is there a way to do it so that class type is automatically determined?
val obj2 = JacksonWrapper.deserialize(json)
println ("obj= " + obj2)
}
}
//http://stackoverflow.com/questions/12591457/scala-2-10-json-serialization-and-deserialization
object JacksonWrapper {
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
def serialize(value: Any): String = {
import java.io.StringWriter
val writer = new StringWriter()
mapper.writeValue(writer, value)
writer.toString
}
def deserialize[T: Manifest](value: String) : T =
mapper.readValue(value, typeReference[T])
private [this] def typeReference[T: Manifest] = new TypeReference[T] {
override def getType = typeFromManifest(manifest[T])
}
private [this] def typeFromManifest(m: Manifest[_]): Type = {
if (m.typeArguments.isEmpty) { m.erasure }
else new ParameterizedType {
def getRawType = m.erasure
def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
def getOwnerType = null
}
}
}

You have to provide the type information somehow. Jackson serializes to JSON only your object's fields, it doesn't store additional information about the type anywhere, so for your Person class, the JSON probably looks like this: { "name": "Bob"}. Without you providing the information that you want to deserialize as an instance of Person, Jackson can't known the type. You could have another class with the field name as well, and JSON doesn't say which one you need. That's why you need to provide the type when deserializing - in Java by passing an argument of type Class and in Scala by providing the type parameter to ObjectMapper and friends.

Related

convert json string to case class object from given json string and type of case class

Requirement is to convert json string to case class object in scala given jsonString and the type of the case class.
I have tried Gson and jackson libraries, but not able to solve the given requirment.
package eg.json
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.gson.Gson
import com.typesafe.scalalogging.LazyLogging
case class Person(name : String, age : Int)
case class Address(street : String, buildingNumber : Int, zipCode : Int)
case class Rent(amount : Double, month : String)
//there are many other case classes
object JsonToObject extends LazyLogging{
import logger._
def toJsonString(ref : Any) : String = {
val gson = new Gson()
val jsonString = gson.toJson(ref)
jsonString
}
def main(args: Array[String]): Unit = {
val person = Person("John", 35)
val jsonString = toJsonString(person)
//here requirement is to convert json string to case class instance, provided the type of case class instance
val gsonObj = toInstanceUsingGson( jsonString, Person.getClass )
debug(s"main : object deserialized using gson : $gsonObj")
val jacksonObj = toInstanceUsingJackson( jsonString, Person.getClass )
debug(s"main : object deserialized using gson : $jacksonObj")
}
def toInstanceUsingGson[T](jsonString : String, caseClassType : Class[T]) : T = {
val gson = new Gson()
val ref = gson.fromJson(jsonString, caseClassType)
ref
}
def toInstanceUsingJackson[T](jsonString : String, caseClassType : Class[T]) : T = {
val mapper = new ObjectMapper()
val ref = mapper.readValue(jsonString, caseClassType)
ref
}
}
Output of execution of above code is :-
01:32:52.369 [main] DEBUG eg.json.JsonToObject$ - main : object deserialized using gson : Person
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "name" (class eg.json.Person$), not marked as ignorable (0 known properties: ])
at [Source: (String)"{"name":"John","age":35}"; line: 1, column: 10] (through reference chain: eg.json.Person$["name"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:60)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:822)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1152)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at eg.json.JsonToObject$.toInstanceUsingJackson(JsonToObject.scala:49)
at eg.json.JsonToObject$.main(JsonToObject.scala:34)
at eg.json.JsonToObject.main(JsonToObject.scala)
Kindly suggest, how to achieve this using gson or jackson, or suggest some other library with sample example.
Above simplified problem is on github :-
https://github.com/moglideveloper/JsonToScalaObject
With Jackson you can do it like this:
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
val mapper = new ObjectMapper() with ScalaObjectMapper
//this line my be needed depending on your case classes
mapper.registerModule(DefaultScalaModule)
def fromJson[T](json: String)(implicit m: Manifest[T]): T = {
mapper.readValue[T](json)
}
I think it is really clean with Jackson lib.
The usage is like this:
val json: String = ???
val personObject: Person = fromJson[Person](json)
Try using circe by Cats.
add circe to your project (https://circe.github.io/circe/ - Quick Start).
create a case class that represent what you want to build from your json.
declare a decoder
https://circe.github.io/circe/codecs/semiauto-derivation.html
https://github.com/circe/circe
import io.circe.parser.decode
import io.circe.syntax._
case class DataToDecode(name : String,
age : Int,
street : String,
buildingNumber : Int,
zipCode : Int,
amount : Double,
month : String)
object DataToDecode{
implicit val dataToDecode: Decoder[DataToDecode] = deriveDecoder
def decodeData(data: Json) : DataToDecode {
data.as[DataToDecode].right.get
}
}
nice example here

Serialize Sealed class within a data class using Gson in kotlin

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.

Cannot deserialize abstract class with type parameter using the Jackson Scala Module

I'm using the Jackson Scala Module to try to serialize and deserialize Scala case classes as JSON.
Specifically, I'm trying to do this with an abstract class that has a type parameter, but the deserialization isn't working. Instead of deserializing the JSON into an object of the type parameter's class, Jackson deserializes it into a Map, so I'm unable to read the object's properties.
Here is the code:
import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
object StackOverflowExample {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "#type")
#JsonSubTypes(Array(
new JsonSubTypes.Type(value = classOf[ResultWrapperSuccess[_]]),
new JsonSubTypes.Type(value = classOf[ResultWrapperFailure[_]])
))
trait ResultWrapperInterface[T] {
protected def obj: T
}
case class ResultWrapperSuccess[T](result: T) extends ResultWrapperInterface[T] {
override protected def obj: T = result
}
case class ResultWrapperFailure[F](failure: F) extends ResultWrapperInterface[F] {
override protected def obj: F = failure
}
case class User(name: String, age: Option[Int])
def main(args: Array[String]): Unit = {
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val user = User("John Smith", Some(39))
val serializedUser = mapper.writeValueAsString(user)
println(s"(1) serializedUser: $serializedUser")
val deserializedUser = mapper.readValue(serializedUser, classOf[User])
println(s"(2) deserializedUser: $deserializedUser")
println(s"(3) deserializedUser.name: ${deserializedUser.name}")
val wrapperSuccess = ResultWrapperSuccess[User](user)
val serializedSuccess = mapper.writeValueAsString(wrapperSuccess)
println(s"(4) serializedSuccess: $serializedSuccess")
val deserializedSuccess = mapper.readValue(serializedSuccess, classOf[ResultWrapperInterface[User]])
deserializedSuccess match {
case _: ResultWrapperFailure[_] =>
case success: ResultWrapperSuccess[User] =>
println(s"(5) success: $success")
println(s"(6) success.result: ${success.result}")
println(s"(7) success.result.name: ${success.result.name}")
}
}
}
The first part when we serialize and deserialize the User object works just fine. The code breaks on (7) when it tries to access success.result.name because success.result is somehow a Map instead of a User.
Here is the output:
(1) serializedUser: {"name":"John Smith","age":39}
(2) deserializedUser: User(John Smith,Some(39))
(3) deserializedUser.name: John Smith
(4) serializedSuccess: {"#type":"StackOverflowExample$ResultWrapperSuccess","result":{"name":"John Smith","age":39}}
(5) success: ResultWrapperSuccess(Map(name -> John Smith, age -> 39))
(6) success.result: Map(name -> John Smith, age -> 39)
Exception in thread "main" java.lang.ClassCastException: scala.collection.immutable.Map$Map2 cannot be cast to StackOverflowExample$User
at StackOverflowExample$.main(StackOverflowExample.scala:55)
at StackOverflowExample.main(StackOverflowExample.scala)
As evidenced by the logs, the serialization seems to be working just fine. Is there something I need to change to get the deserialization working?

Jackson mapper with generic class in scala

I am trying to serialise GeneralResponse:
case class GeneralResponse[T](succeeded: Boolean, payload: Option[T])
and the payload is GroupsForUserResult:
case class GroupsForUserResult(groups: Seq[UUID]).
I am using mapper.readValue(response.body, classOf[GeneralResponse[GroupsForUserResult]]) but unfortunately the payload is serialised as a Map and not as the desired case class (GroupForUserResult).
Because of Java Erasure - Jackson can't know at runtime about the generic type T from the line -
mapper.readValue(response.body, classOf[GeneralResponse[GroupsForUserResult]])
A solution to this problem will be
import com.fasterxml.jackson.core.`type`.TypeReference
mapper.readValue(json, new TypeReference[GeneralResponse[GroupsForUserResult]] {})
This way you provide an instance of TypeReference with all the needed Type information.
The accepted answer is close enough but you also have to provide the Type Parameter to .readValue method,
Working example with test,
import com.fasterxml.jackson.core.`type`.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import org.scalatest.{FunSuite, Matchers}
case class Customer[T](name: String, address: String, metadata: T)
case class Privileged(desc: String)
class ObjectMapperSpecs extends FunSuite with Matchers {
test("deserialises to case class") {
val objectMapper = new ObjectMapper()
.registerModule(DefaultScalaModule)
val value1 = new TypeReference[Customer[Privileged]] {}
val response = objectMapper.readValue[Customer[Privileged]](
"""{
"name": "prayagupd",
"address": "myaddress",
"metadata": { "desc" : "some description" }
}
""".stripMargin, new TypeReference[Customer[Privileged]] {})
response.metadata.getClass shouldBe classOf[Privileged]
response.metadata.desc shouldBe "some description"
}
}
The signature of com.fasterxml.jackson.databind.ObjectMapper#readValue,
public <T> T readValue(String content, TypeReference valueTypeRef)
throws IOException, JsonParseException, JsonMappingException
{
return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueTypeRef));
}
If you don't provide the type parameter, it will blow up with error Customer cannot be cast to scala.runtime.Nothing$

How do you exclude fields in Jackson json serialisation (Scala)

I am trying to exclude the _passthroughFields property in the example below. When I use the debugger, it looks like my PropertyFilter is never used. What am I doing wrong?
import java.util
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.SerializeExceptFilter
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider
import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper, ObjectWriter}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import org.scalatest.{Matchers, WordSpec}
import scala.collection.immutable.Map
class PassthroughFieldsSpec extends WordSpec with Matchers {
"JacksonParser" when {
"given an Object and undesired fields" should {
"not include those fields in the json response" in {
trait Foo {
def id: String
def _passthroughFields: Map[String, String] = Map.empty
}
class Bar(val id: String, override val _passthroughFields: Map[String, String]) extends Foo
val item = new Bar("abcd", Map.empty)
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val excludes = new util.HashSet[String](1)
excludes.add("_passthroughFields")
excludes.add("get_passthroughFields")
val filters = new SimpleFilterProvider()
.addFilter("filter properties by name", new SerializeExceptFilter(excludes))
val writer: ObjectWriter = mapper.writer(filters)
val json = writer.writeValueAsString(item)
json.contains("_passthroughFields") shouldBe false
}
}
}
}
I think you can exclude it using something like #JsonIgnore or make the field transient.
Otherwise if you need to define it outside of the case class code (like in your example) you can do it with Genson.
import com.owlike.genson._
import com.owlike.genson.reflect.VisibilityFilter
// Note that if you use case classes you don't need the visibility filter stuff
// it is used to indicate that private fields should be ser/de
val genson = new GensonBuilder()
.withBundle(ScalaBundle())
.useFields(true, VisibilityFilter.PRIVATE)
.exclude("_passthroughFields")
.create()
genson.toJson(item)
Disclaimer: I am Gensons author.