Decode generic type with Jackson - java

Jackson doesn't attempt to decode generic elements of a class.
How do you pass in the the type information without creating a specific version of the class?
The data types are:
#JsonIgnoreProperties(ignoreUnknown = true)
data class HasuraTriggerPayload<T>(
val event: HasuraEvent<T>,
#JsonProperty("created_at")
val createdAt: Instant,
val trigger: Trigger
)
#JsonIgnoreProperties(ignoreUnknown = true)
data class HasuraEvent<T>(
val op: String,
val data: HasuraData<T>
)
data class HasuraData<T>(
val old: T,
val new: T
)
data class Trigger(
val name: String
)
data class Price(
val id: UUID,
val price: BigDecimal?,
)
My route is something like:
post("/price") {
val payload = call.receive<HasuraTriggerPayload<Price>>()
// etc
}
Which produces this error because it doesn't know how to decode new/old:
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.scraptickets.rest.models.Price (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.scraptickets.rest.models.Price is in unnamed module of loader 'app')
trigger.name gives the type. in this case, it's "price-trigger"
There are two possible solutions to this.
We know that T is Price at our particular call site, so we could somehow pass along that information, but how?
We can determine the type of old and new from trigger.name, but how do we write that? The only obvious way is from reading a field of the object we're trying to determine the class for, not from outside.

After a bunch of searching, I found the solution.
You need to create a TypeReference.
For my use the solution was:
post("/price") {
val typeRef = object : TypeReference<HasuraTriggerPayload<Price>>() { }
val objectMapper = jacksonObjectMapper() // Not quite this, I'm leaving out the part to handle timestamps
val jsonStr = call.receiveText()
val payload = objectMapper.readValue(jsonStr, typeRef)
// etc
}

Related

How to deserialize field with Interface field in Java Jackson?

I'm trying to send json objects between two Spring Boot services with rpc so for that I need JacksonMessageConverter.
I need send object with field that is an Interface type of multiple DTO classes.
For instance (code in Kotlin):
Response class
class Response(
#JsonProperty("result")
val result: IExampleData?,
#JsonProperty("statusCode")
val statusCode: Int
)
ExampleData classes
interface IExampleData
class ExampleData1(
#JsonProperty("exampleString")
val exampleString: String
) : IExampleData
class ExampleData2(
#JsonProperty("exampleInt")
val exampleInt: Int
) : IExampleData
If I use #JsonDeserialize annotation for one of Example objects, it works fine with correct object:
Response class
...
#JsonDeserialize(`as` = ExampleData1::class)
val result: IExampleData?
...
But it won't work if service send ExampleData2. I need that for all of my objects that implements IExampleData.
I tried to use #JsonSubTypes annotation for result field like that:
...
#JsonSubTypes(
Type(value = ExampleData1::class, name = "exampleData1"),
Type(value = ExampleData2::class, name = "exampleData2"),
)
val result: IExampleData?
...
But it didn't work for me. I get next error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.demo.dto.IExampleData` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
So how can I deserialize one of multiple objects for one field?

With the latest version of phantom(2.42.0) and scala(2.12.0-RC2) No implicit found for parameter ev:Nothing while querying the database

I am upgrading to latest version of phantom-dsl 2.42.0 and scala 2.12 and according to migration guide now I do not require to generate fromRow() method but while querying the database I am getting no implicit parameter found (compile time).
I tried writing fromRow method but still it did'nt work
My case class is -
case class Ball(matchId: Int,
inningsid: Int,
ballNbr: Int,
timeScore: Long,
batStrikerDots: Option[Int],
batStrikerFours: Option[Int],
commFormats: Map[String, CommentaryFormats],
.......
......)
class Ball extends Table[BallData, Ball] with CassandraSession with CommentaryPrimitive {
object matchId extends IntColumn with PartitionKey {
override lazy val name = col_name
}
object inningsId extends IntColumn with PrimaryKey with ClusteringOrder with Descending {
override lazy val name = col_name
}
object ballNbr extends IntColumn with PrimaryKey with ClusteringOrder with Descending {
override lazy val name = col_name
}
object timeScore extends DateTimeColumn with PrimaryKey with ClusteringOrder with Descending {
override lazy val name = col_name
}
object commentaryFormat extends MapColumn[String, CommentaryFormats] {
override lazy val name = "comm_format"
}
............
............
}
col_name are respective column names.
where comm_format is UDT so I have CommentaryPrimitive defined for that which registers a codec on how to deserialize cassandra data to scala case class and CassandaraSession provides implicit keyspace and session.
This is my Primitive code to register the codec -
override def cassandraType: String = {
val format_types = CassandraConnector.getSession.getCluster.getMetadata.getKeyspace("matches_data").getUserType("format_types")
val formatCodecCodec = new CommentaryCodec(TypeCodec.userType(format_types), classOf[CommentaryFormats])
CassandraConnector.registerCodec(formatCodecCodec)
format_types.toString()
}
override def asCql(value: PrimitiveType): String = ???
override def dataType: String = ???
override def serialize(obj: PrimitiveType, protocol: ProtocolVersion): ByteBuffer = ???
override def deserialize(source: ByteBuffer, protocol: ProtocolVersion): PrimitiveType = ???
but while querying I am not able to resolve this-
select.where(_.matchId eqs matchId)(...).one()(...) --> retrieving Ball object
... -> no implicit found
with a new version of phantom-dsl it should automatically resolve Ball data but it is throwing error no implicit found.

Populating a Kotlin Data Class using Gson

I am attempting to populate a Kotlin data class from a corresponding string value. I looked at: Kotlin Data Class from Json using GSON but what I am attempting to do is not tracking exactly the same:
fun convertStringToObject(stringValue: String?, clazz: Class<*>): Any? {
return if (stringValue != null) {
try {
val gson = Gson()
gson.fromJson<Any>(stringValue, clazz)
}
catch (exception: Exception) {
// yes, we are swallowing the possible
// java.lang.IllegalStateException
null
}
}
else {
null
}
}
Calling this function and attempting to populate the following class:
data class LoggedUser(
#SerializedName("id") val id: Long,
#SerializedName("name") val name: String,
#SerializedName("first_name") val firstName: String,
#SerializedName("last_name") val lastName: String,
#SerializedName("email") val email: String
)
It executes but LoggedUser values are empty (null).
The stringValue is currently:
{"nameValuePairs":{"id":"1654488452866661","name":"Bob Smith","email":"bob.smith#test.net","first_name":"Bob","last_name":"Smith"}}
I even tried using a hardcoded class value like this:
gson.fromJson(stringValue, LoggedUser::class.java)
but there was no joy. The stringValue is what gson.toJson(value) generated where value was a JSONObject.
Anybody have an idea what my disconnect is?
Your JSON string actually has two JSON objects, the outer value (which has a key called nameValuePairs) and the value you actually want to deserialize (which is the value at key nameValuePairs). You need to dive one level deeper, either through an outer wrapper class which holds your User object or by manually getting the JsonObject at key nameValuePairs as a String and then passing that to Gson. If the string was just {"id":"1654488452866661","name":"Bob Smith","email":"bob.smith#test.net","first_name":"Bob","last_name":"Smith"} it would deserialize fine.
Create a wrapper class
data class LoggedUserWrapper{
#SerializedName("nameValuePairs") val nameValuePairs: LoggedUser
}
and execute
val loggedUser = convertStringToObject(yourJsonString, LoggedUserWrapper::class.java)
This will help you.

Better way to map Kotlin data objects to data objects

I want to convert/map some "data" class objects to similar "data" class objects. For example, classes for web form to classes for database records.
data class PersonForm(
val firstName: String,
val lastName: String,
val age: Int,
// maybe many fields exist here like address, card number, etc.
val tel: String
)
// maps to ...
data class PersonRecord(
val name: String, // "${firstName} ${lastName}"
val age: Int, // copy of age
// maybe many fields exist here like address, card number, etc.
val tel: String // copy of tel
)
I use ModelMapper for such works in Java, but it can't be used because data classes are final (ModelMapper creates CGLib proxies to read mapping definitions). We can use ModelMapper when we make these classes/fields open, but we must implement features of "data" class manually.
(cf. ModelMapper examples: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)
How to map such "data" objects in Kotlin?
Update:
ModelMapper automatically maps fields that have same name (like tel -> tel) without mapping declarations. I want to do it with data class of Kotlin.
Update:
The purpose of each classes depends on what kind of application, but these are probably placed in the different layer of an application.
For example:
data from database (Database Entity) to data for HTML form (Model/View Model)
REST API result to data for database
These classes are similar, but are not the same.
I want to avoid normal function calls for these reasons:
It depends on the order of arguments. A function for a class with many fields that have same type (like String) will be broken easily.
Many declarations are nesessary though most mappings can be resolved with naming convention.
Of course, a library that has similar feature is intended, but information of the Kotlin feature is also welcome (like spreading in ECMAScript).
Simplest (best?):
fun PersonForm.toPersonRecord() = PersonRecord(
name = "$firstName $lastName",
age = age,
tel = tel
)
Reflection (not great performance):
fun PersonForm.toPersonRecord() = with(PersonRecord::class.primaryConstructor!!) {
val propertiesByName = PersonForm::class.memberProperties.associateBy { it.name }
callBy(args = parameters.associate { parameter ->
parameter to when (parameter.name) {
"name" -> "$firstName $lastName"
else -> propertiesByName[parameter.name]?.get(this#toPersonRecord)
}
})
}
Cached reflection (okay performance but not as fast as #1):
open class Transformer<in T : Any, out R : Any>
protected constructor(inClass: KClass<T>, outClass: KClass<R>) {
private val outConstructor = outClass.primaryConstructor!!
private val inPropertiesByName by lazy {
inClass.memberProperties.associateBy { it.name }
}
fun transform(data: T): R = with(outConstructor) {
callBy(parameters.associate { parameter ->
parameter to argFor(parameter, data)
})
}
open fun argFor(parameter: KParameter, data: T): Any? {
return inPropertiesByName[parameter.name]?.get(data)
}
}
val personFormToPersonRecordTransformer = object
: Transformer<PersonForm, PersonRecord>(PersonForm::class, PersonRecord::class) {
override fun argFor(parameter: KParameter, data: PersonForm): Any? {
return when (parameter.name) {
"name" -> with(data) { "$firstName $lastName" }
else -> super.argFor(parameter, data)
}
}
}
fun PersonForm.toPersonRecord() = personFormToPersonRecordTransformer.transform(this)
Storing Properties in a Map
data class PersonForm(val map: Map<String, Any?>) {
val firstName: String by map
val lastName: String by map
val age: Int by map
// maybe many fields exist here like address, card number, etc.
val tel: String by map
}
// maps to ...
data class PersonRecord(val map: Map<String, Any?>) {
val name: String by map // "${firstName} ${lastName}"
val age: Int by map // copy of age
// maybe many fields exist here like address, card number, etc.
val tel: String by map // copy of tel
}
fun PersonForm.toPersonRecord() = PersonRecord(HashMap(map).apply {
this["name"] = "${remove("firstName")} ${remove("lastName")}"
})
Is this are you looking for?
data class PersonRecord(val name: String, val age: Int, val tel: String){
object ModelMapper {
fun from(form: PersonForm) =
PersonRecord(form.firstName + form.lastName, form.age, form.tel)
}
}
and then:
val personRecord = PersonRecord.ModelMapper.from(personForm)
MapStruct lets kapt generate classes doing the mapping (without reflection).
Use MapStruct:
#Mapper
interface PersonConverter {
#Mapping(source = "phoneNumber", target = "phone")
fun convertToDto(person: Person) : PersonDto
#InheritInverseConfiguration
fun convertToModel(personDto: PersonDto) : Person
}
// Note this either needs empty constructor or we need #KotlinBuilder as dsecribe below
data class Person: this(null, null, null, null) (...)
Use:
val converter = Mappers.getMapper(PersonConverter::class.java) // or PersonConverterImpl()
val person = Person("Samuel", "Jackson", "0123 334466", LocalDate.of(1948, 12, 21))
val personDto = converter.convertToDto(person)
println(personDto)
val personModel = converter.convertToModel(personDto)
println(personModel)
Edit:
Now with #KotlinBuilder for avoiding constructor() issue:
GitHub: Pozo's mapstruct-kotlin
Annotate data classes with #KotlinBuilder. This will create a PersonBuilder class, which MapStruct uses, thus we avoid ruining the interface of the data class with a constructor().
#KotlinBuilder
data class Person(
val firstName: String,
val lastName: String,
val age: Int,
val tel: String
)
Dependency :
// https://mvnrepository.com/artifact/com.github.pozo/mapstruct-kotlin
api("com.github.pozo:mapstruct-kotlin:1.3.1.1")
kapt("com.github.pozo:mapstruct-kotlin-processor:1.3.1.1")
https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin
Using ModelMapper
/** Util.kt **/
class MapperDto() : ModelMapper() {
init {
configuration.matchingStrategy = MatchingStrategies.LOOSE
configuration.fieldAccessLevel = Configuration.AccessLevel.PRIVATE
configuration.isFieldMatchingEnabled = true
configuration.isSkipNullEnabled = true
}
}
object Mapper {
val mapper = MapperDto()
inline fun <S, reified T> convert(source: S): T = mapper.map(source, T::class.java)
}
Usage
val form = PersonForm(/** ... **/)
val record: PersonRecord = Mapper.convert(form)
You might need some mapping rules if the field names differ. See the getting started
PS: Use kotlin no-args plugin for having default no-arg constructor with your data classes
Do you really want a separate class for that? You can add properties to the original data class:
data class PersonForm(
val firstName: String,
val lastName: String,
val age: Int,
val tel: String
) {
val name = "${firstName} ${lastName}"
}
This works using Gson:
inline fun <reified T : Any> Any.mapTo(): T =
GsonBuilder().create().run {
toJson(this#mapTo).let { fromJson(it, T::class.java) }
}
fun PersonForm.toRecord(): PersonRecord =
mapTo<PersonRecord>().copy(
name = "$firstName $lastName"
)
fun PersonRecord.toForm(): PersonForm =
mapTo<PersonForm>().copy(
firstName = name.split(" ").first(),
lastName = name.split(" ").last()
)
with not nullable values allowed to be null because Gson uses sun.misc.Unsafe..
For ModelMapper you could use Kotlin's no-arg compiler plugin, with which you can create an annotation that marks your data class to get a synthetic no-arg constructor for libraries that use reflection. Your data class needs to use var instead of val.
package com.example
annotation class NoArg
#NoArg
data class MyData(var myDatum: String)
mm.map(. . ., MyData::class.java)
and in build.gradle (see docs for Maven):
buildscript {
. . .
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
apply plugin: 'kotlin-noarg'
noArg {
annotation "com.example.NoArg"
}
You can use ModelMapper to map to a Kotlin data class. The keys are:
Use #JvmOverloads (generates a constructor with no arguments)
Default values for data class member
Mutable member, var instead of val
data class AppSyncEvent #JvmOverloads constructor(
var field: String = "",
var arguments: Map<String, *> = mapOf<String, Any>(),
var source: Map<String, *> = mapOf<String, Any>()
)
val event = ModelMapper().map(request, AppSyncEvent::class.java)
You can use the DataClassMapper class taken from here: https://github.com/jangalinski/kotlin-dataclass-mapper
data class PersonForm(
val firstName: String,
val lastName: String,
val age: Int,
// maybe many fields exist here like address, card number, etc.
val tel: String
)
// maps to ...
data class PersonRecord(
val name: String, // "${firstName} ${lastName}"
val age: Int, // copy of age
// maybe many fields exist here like address, card number, etc.
val tel: String // copy of tel
)
fun mapPerson(person: PersonForm): PersonRecord =
DataClassMapper<PersonForm, PersonRecord>()
.targetParameterSupplier(PersonRecord::name) { "${it.firstName} ${it.lastName}"}
.invoke(person)
fun main() {
val result = mapPerson(PersonForm("first", "last", 25, "tel"))
println(result)
}
Result will be:
PersonRecord(name=first last, age=25, tel=tel)
Yet another mapper - LaMapper. E.g.
fun PersonForm.toPersonRecord() = LaMapper.copyFrom(this) {
PersonRecord::name from { "${it.firstName} ${it.lastName}" }
// add more mappings, everything else mapped by name
}
val rec = person.toPersonRecord()
In addition it has various data-type conversions by default.
kMapper-object to object mapper specifically created for Kotlin. Uses compile time code generation, so no reflection.
Interface description for a mapper looks like that:
#Mapper
internal interface BindMapper {
fun map(dto: BindDto, #Bind second: Int, #Bind third: SomeInternalDto, #Bind(to = "fourth") pr: Double): BindDomain
}
More examples here.
Disclaimer: I'm the author.

Using Jackson to (De)-serialize a Scala Case Class

I tested out the serialization of a Scala case class using Jackson.
DeserializeTest.java
public static void main(String[] args) throws Exception { // being lazy to catch-all
final ObjectMapper mapper = new ObjectMapper();
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
mapper.writeValue(stream, p.Foo.personInstance());
System.out.println("result:" + stream.toString());
}
}
Foo.scala
object Foo {
case class Person(name: String, age: Int, hobbies: Option[String])
val personInstance = Person("foo", 555, Some("things"))
val PERSON_JSON = """ { "name": "Foo", "age": 555 } """
}
When I ran the above main of the Java class, an exception was thrown:
[error] Exception in thread "main" org.codehaus.jackson.map.JsonMappingException:
No serializer found for class p.Foo$Person and no properties discovered
to create BeanSerializer (to avoid exception,
disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
How can I (de)-serialize Scala case classes?
Jackson is expecting your class to be a JavaBean, which means its expects the class to have a getX() and/or setX() for every property.
Option 1
You can create JavaBean classes in Scala using the annotation BeanProperty.
Example
case class Person(
#BeanProperty val name: String,
#BeanProperty val age: Int,
#BeanProperty val hobbies: Option[String]
)
In this case a val will mean only a getter is defined. If you want setters for deserialization you defined the properties as var.
Option 2
While option 1 will work, if you really want to use Jackson there are wrappers that allow it to deal with Scala classes like FasterXML's scala module which might be a better approach. I haven't used it as I've just been using the Json library built in to play.
Found a solution that works with jackson and scala case classes.
I used a scala module for jackson - jackson-module-scala.
libraryDependencies ++= Seq(
"com.fasterxml.jackson.core" % "jackson-databind" % "2.5.3",
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.2.2"
)
I had to annotate fields in my case class with #JsonProperty.
This is what my case class looks like:
case class Person(#JsonProperty("FName") FName: String, #JsonProperty("LName") LName: String)
And this is how I deserialize:
val objectMapper = new ObjectMapper() with ScalaObjectMapper
objectMapper.registerModule(DefaultScalaModule)
val str = """{"FName":"Mad", "LName": "Max"}"""
val name:Person = objectMapper.readValue[Person](str)
Serialization is easier:
val out = new ByteArrayOutputStream()
objectMapper.writeValue(out, name)
val json = out.toString
Would like to clarify that I am using
com.fasterxml.jackson.databind.ObjectMapper
In the question, it seems he is using
org.codehaus.jackson.map.ObjectMapper
which won't work with ScalaObjectMapper.
Based on Priyank Desai's answer I have created a generic function to convert json string to case class
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
def jsonToType[T](json:String)(implicit m: Manifest[T]) :T = {
val objectMapper = new ObjectMapper() with ScalaObjectMapper
objectMapper.registerModule(DefaultScalaModule)
objectMapper.readValue[T](json)
}
Usage:
case class Person(#JsonProperty("name") Name:String, #JsonProperty("age") Age:Int)
val personName = jsonToType[Person](jsonString).name
Deserializing objects is way easier with upickle than Jackson. Here's an example:
case class City(name: String, funActivity: String, latitude: Double)
implicit val cityRW = upickle.default.macroRW[City]
val str = """{"name":"Barcelona","funActivity":"Eat tapas","latitude":41.39}"""
val barcelona = upickle.default.read[City](str)
The Jackson solutions are way more verbose. See this post to learn more about this approach.
I have created a generic function to convert JSON String to Case Class/Object and Case Class/Object to JSON String.
Please find a working and detailed answer which I have provided using generics here.
If you are using Spring MVC, use a configuration like this:
import java.util.List
#Configuration
#EnableWebMvc
class WebConfig extends WebMvcConfigurer {
override def configureMessageConverters(converters: List[HttpMessageConverter[_]]): Unit = {
val conv = new MappingJackson2HttpMessageConverter();
val mapper = JsonMapper.builder()
.addModule(DefaultScalaModule)
.build();
conv.setObjectMapper(mapper);
converters.add(conv);
}
}
Then serializing / deserializing plain case classes and Collection types will work as expected.

Categories

Resources