Missing identity field with polymorphic (de)serialisation in Kotlin with Jackson - json

I have the following class hierarchy annotated as such:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes(
JsonSubTypes.Type(value = NetCommand.AddEntity::class, name = "AddEntity"),
JsonSubTypes.Type(value = NetCommand.RemoveEntity::class, name = "RemoveEntity"),
JsonSubTypes.Type(value = NetCommand.MoveEntity::class, name = "MoveEntity"),
JsonSubTypes.Type(value = NetCommand.SpeakEntity::class, name = "SpeakEntity"),
JsonSubTypes.Type(value = NetCommand.AddItem::class, name = "AddItem")
)
sealed class NetCommand {
class AddEntity(val id: Long, val position: TilePosition, val table: Character) : NetCommand()
class RemoveEntity(val id: Long) : NetCommand()
class MoveEntity(val id: Long, val position: TilePosition) : NetCommand()
class SpeakEntity(val id: Long, val username: String, val message: String) : NetCommand()
class AddItem(val id: Long, val item: Item) : NetCommand()
}
The idea being I can communicate a collection (ArrayList) of NetCommand to a second application and have them be correctly deserialised into the appropriate subclass.
I have also written a simple test to help me iterate on different configurations of the annotations/jackson mapper:
val command = NetCommand.AddEntity(1, TilePosition(0, 0), Character.KNIGHT)
val commandList: ArrayList<NetCommand> = ArrayList()
commandList.add(command)
val mapper = jacksonObjectMapper()
val commandListString = mapper.writeValueAsString(commandList)
val resultList = mapper.readValue<ArrayList<NetCommand>>(commandListString)
assert(resultList[0] as? NetCommand.AddEntity != null)
assert((resultList[0] as NetCommand.AddEntity).id == command.id)
This fails on the line:
val resultList = mapper.readValue<ArrayList<NetCommand>>(commandListString)
With this error:
Missing type id when trying to resolve subtype of [simple type, class shared.NetCommand]: missing type id property 'type'
at [Source: (String)"[{"id":1,"position":{"x":0,"y":0},"table":"KNIGHT"}]"; line: 1, column: 51] (through reference chain: java.util.ArrayList[0])
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class shared.NetCommand]: missing type id property 'type'
at [Source: (String)"[{"id":1,"position":{"x":0,"y":0},"table":"KNIGHT"}]"; line: 1, column: 51] (through reference chain: java.util.ArrayList[0])
Any ideas why my type field isn't being serialised?
(Less than ideal) Solution
I found a solution in manually adding an already initialised field to the body of subclasses with the name of the subclass. Eg.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes(
JsonSubTypes.Type(value = AddEntity::class, name = "AddEntity"),
JsonSubTypes.Type(value = RemoveEntity::class, name = "RemoveEntity"),
JsonSubTypes.Type(value = MoveEntity::class, name = "MoveEntity"),
JsonSubTypes.Type(value = SpeakEntity::class, name = "SpeakEntity"),
JsonSubTypes.Type(value = AddItem::class, name = "AddItem")
)
sealed class NetCommand { val type: String = javaClass.simpleName }
class AddEntity(val id: Long, val position: TilePosition, val table: Character) : NetCommand()
class RemoveEntity(val id: Long) : NetCommand()
class MoveEntity(val id: Long, val position: TilePosition) : NetCommand()
class SpeakEntity(val id: Long, val username: String, val message: String) : NetCommand()
class AddItem(val id: Long, val item: Item) : NetCommand()
Ideally I'd like to just use the simple class name automatically rather than having name = "AddEntity" etc. on each JsonSubTypes.Type call.

I think I've found the best solution I'm gonna find. Using the JsonTypeInfo.Id.CLASS for the mapping I no longer need to provide names for each subtype - it just relies on the fully qualified class name. This automatically uses the field name #class which I can automatically populate on the super class NetCommand using the #JsonProperty annotation to name the field correctly. Also worth noting is we don't need to provide the #JsonSubTypes annotation at all.
Would rather be using the SimpleName (eg. AddItem instead of my.fully.qualified.path.AddItem) but haven't figured that out yet.
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
sealed class NetCommand { #JsonProperty("#class") val type = javaClass.canonicalName }
class AddEntity(val id: Long, val position: TilePosition, val table: Character) : NetCommand()
class RemoveEntity(val id: Long) : NetCommand()
class MoveEntity(val id: Long, val position: TilePosition) : NetCommand()
class SpeakEntity(val id: Long, val username: String, val message: String) : NetCommand()
class AddItem(val id: Long, val item: Item) : NetCommand()

As an addition to the OP's solution and ryfterek comment, the following annotation would have taken care of explicitly declaring, mentioned #JsonProperty("#class") val type = javaClass.canonicalName property:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type").
Where 'type' is the name of the field that will be declared in POJO.

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

How to parse POJO with polymorphic field which contains JSON as String?

I have the following JSON:
{
"type":"concrete",
"poly":"{\"value\":\"CONCRETE VALUE\"}"
}
As you see "poly" field is a JSON. And its a polymorph
And I have that POJO`s.
class Data(
#JsonTypeId
val type: String,
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type", visible = true)
val poly: Base
)
#JsonSubTypes(
JsonSubTypes.Type(value = Concrete::class, name = "concrete"),
JsonSubTypes.Type(value = AnotherConcrete::class, name = "another")
)
interface Base
#JsonTypeName("concrete")
data class Concrete(val value: String) : Base
#JsonTypeName("another")
data class AnotherConcrete(val anotherValue: String) : Base
But when I try read value through object mapper, throws error
InvalidDefinitionException: Cannot construct instance of Base

Jackson combining #JsonIdentityInfo and #JsonTypeInfo throws InvalidTypeIdException

Currently I am having an issue with Jackson when I combine #JsonIdentityInfo and #JsonTypeInfo. The Kotlin code below throws an exception on the last line. It serializes the dog1AndDog1Json instance as expected into Json but it then throws an exception while deserializing it back into an instance.
package some.test
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.databind.ObjectMapper
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
#JsonSubTypes(JsonSubTypes.Type(value = Dog::class), JsonSubTypes.Type(value = Cat::class))
interface Animal {
val name: String
}
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator::class)
data class Dog(#JsonProperty("name") override val name: String) : Animal
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator::class)
data class Cat(#JsonProperty("name") override val name: String) : Animal
data class TwoAnimals(#JsonProperty("animal1") val animal1: Animal, #JsonProperty("animal2") val animal2: Animal)
fun main() {
val om = ObjectMapper();
val dog1 = Dog("Dog1")
val dog2 = Dog("Dog2")
val cat1 = Cat("Cat1")
val dog1AndDog2 = TwoAnimals(dog1, dog2)
val dog1AndDog2Json = om.writerWithDefaultPrettyPrinter().writeValueAsString(dog1AndDog2)
assert(dog1AndDog2 === om.readValue(dog1AndDog2Json, TwoAnimals::class.java)) // OK
val dog1AndCat1 = TwoAnimals(dog1, cat1)
val dog1AndCat2Json = om.writerWithDefaultPrettyPrinter().writeValueAsString(dog1AndCat1)
assert(dog1AndCat1 === om.readValue(dog1AndCat2Json, TwoAnimals::class.java)) // OK
val dog1AndDog1 = TwoAnimals(dog1, dog1)
val dog1AndDog1Json = om.writerWithDefaultPrettyPrinter().writeValueAsString(dog1AndDog1)
println(dog1AndDog1Json)
assert(dog1AndDog1 === om.readValue(dog1AndDog1Json, TwoAnimals::class.java)) // DESERIALIZE FAILS
}
Then I run the main function I get the following output:
{
"animal1" : {
"#class" : "some.test.Dog",
"#id" : 1,
"name" : "Dog1"
},
"animal2" : 1
}
Followed by this exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class some.test.Animal]: missing type id property '#class' (for POJO property 'animal2')
at [Source: (String)"{
"animal1" : {
"#class" : "some.test.Dog",
"#id" : 1,
"name" : "Dog1"
},
"animal2" : 1
}"; line: 7, column: 15] (through reference chain: some.test.TwoAnimals["animal2"])
at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
<truncated rest of stacktrace>
It seems that Jackson expects an object at the animal2 property which has a #class property to find the correct class type to be deserialized. But it has been replaced with an id by the #JsonIdentityInfo annotation. Why does Jackson not look up the object by that id and then check the #class property of that instance?
I am not sure if this use case is not supported by Jackson or I am doing something wrong (what I am hoping for). Or maybe it is a bug?
I managed to to get it working by:
Removing the #JsonIdentityInfo annotation from the Dog and Car sub classes
Adding a #JsonIdentityInfo to the Animal base class

Kotlin: How can I avoid code duplication in constructors?

Often I find myself in a situation where I have a superclass that has lots of optional parameters, and those same parameters need to also be optional parameters in its subclasses.
For example, the superclass:
abstract class Plugin(val name: String, val version: String = "1.0",
val author: String = "", val description: String = "")
Extending this class is a pain. Here's an example subclass:
abstract class CyclePlugin(name: String, version: String = "1.0", author: String = "",
description: String = "", val duration: Int, val durationUnit: TimeUnit
= MILLISECONDS) : Plugin(name, version, author, description)
Note: I will answer this question with my solution. I am in search of a better solution.
The way I normally solve this problem is by creating a data class to represent the parameters.
data class PluginInfo(val name: String, val version: String = "1.0",
val author: String = "", val description: String = "")
I then take this class as a parameter in the constructors.
abstract class Plugin(val info: PluginInfo)
abstract class CyclePlugin(info: PluginInfo, val duration: Int,
val durationUnit: TimeUnit = MILLISECONDS) : Plugin(info)
Then an example plugin can be implemented like this:
class ExamplePlugin : CyclePlugin(PluginInfo("Example Plugin", author = "Jire"), 8, TimeUnit.SECONDS)
Like #miensol mentioned, you can define your properties outside of the constructor.
abstract class Plugin(val name: String) {
open val version: String = "1.0"
open val author: String = ""
open val description: String = ""
}
Then you're able to define CyclePlugin with only the necessary name parameter:
abstract class CyclePlugin(name: String, val duration: Int,
val durationUnit: TimeUnit = MILLISECONDS) : Plugin(name)
Then for example, you can override some fields for ExamplePlugin:
class ExamplePlugin : CyclePlugin("Example Plugin", 8, TimeUnit.SECONDS) {
override val author = "Giovanni"
override val description = "This is an example plugin"
}

Error in converting class with enum attribute to Json with Play 2.3 and Scala 2.11.1

i have a case class User with an Enum as one of its attributes
i want to convert this class into Json using Play-Json api but i am getting error
here is my User class
case class User ( name : String= "", id : String = "", status : UserStatus = ACTIVE)
object User{
implicit val userFmt = Json.format[User]
}
UserStatus
object UserStatus extends Enumeration {
type UserStatus = Value
val ACTIVE , INACTIVE , BLOCKED , DELETED = Value
implicit val statusFmt = Json.format[UserStatus]
}
it gives me following error in User class
No implicit format for UserStatus available
on this line
implicit val userFmt = Json.format[User]
and following error in UserStatus(enum)
No unapply function found
on this line
implicit val statusFmt = Json.format[UserStatus]
please help me!
You only needed a formatter for your enum. I have rewritten your example using this as a way to write the formatter:
import play.api.libs.json._
object UserStatus extends Enumeration {
type UserStatus = Value
val ACTIVE , INACTIVE , BLOCKED , DELETED = Value
}
case class User ( name : String= "", id : String = "", status : UserStatus.UserStatus = UserStatus.ACTIVE)
object User{
implicit val myEnumFormat = new Format[UserStatus.UserStatus] {
def reads(json: JsValue) = JsSuccess(UserStatus.withName(json.as[String]))
def writes(myEnum: UserStatus.UserStatus) = JsString(myEnum.toString)
}
implicit val userFmt = Json.format[User]
}
println(Json.toJson(User("1", "2", UserStatus.ACTIVE)))