Jackson Deserializer default to null on failure - java

I have an issue where an upstream service is making requests and about 5% of the requests that are made are partially malformed. For example, instead of a nullable property coming to me as the object type I am expecting, I get a random string.
data class FooDTO(
val bar: BarDTO?,
val name: String
)
data class BarDTO(
val size: Int
)
But the payload I get looks like
{
"name": "The Name",
"bar": "uh oh random string instead of object"
}
I don't want to fail the request when this happens because the part of the data that is correct is still useful for my purposes, so what I want to do is just default the deserialization failure to null. I also have a few different sub-objects in my FooDTO that do this so I want a generic way to solve it for those specific fields. I know I can write a custom deserializer like the following to solve it on a 1-off basis.
class BarDtoDeserializer #JvmOverloads constructor(vc: Class<*>? = null) : StdDeserializer<BarDTO?>(vc) {
override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): AnalysisDTO? {
return try {
val node = jp.codec.readTree<JsonNode>(jp)
BarDTO(size = node.get("size").numberValue().toInt())
} catch (ex: Throwable) {
null
}
}
}
And then I can decorate my BarDTO object with a #JsonDeserialize(using=BarDtoDeserializer::class) to force it to use that deserializer. What I am hoping to do is have some way to do this in a generic way.

Thanks to this answer https://stackoverflow.com/a/45484422/4161471 I've found that a DeserializationProblemHandler can be used to return 'null' for invalid data.
In your instance, override the handleMissingInstantiator() function. If other payloads have other types of bad data, other overrides may be required.
Also, I thought that CoercionConfig might be the answer, but I couldn't get it to work or find decent documentation. That might be another path.
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
data class FooDTO(
val name: String,
val bar: BarDTO?,
)
data class BarDTO(
val size: Int
)
fun main() {
val mapper = jacksonObjectMapper()
// mapper
// .coercionConfigFor(LogicalType.Textual)
// .setCoercion(CoercionInputShape.String, CoercionAction.AsNull)
mapper.addHandler(object : DeserializationProblemHandler() {
override fun handleMissingInstantiator(
ctxt: DeserializationContext?,
instClass: Class<*>?,
valueInsta: ValueInstantiator?,
p: JsonParser?,
msg: String?
): Any? {
println("returning null for value, $msg")
return null
}
})
val a: FooDTO = mapper.readValue(
"""
{
"name": "I'm variant A",
"bar": "uh oh random string instead of object"
}
"""
)
val b: FooDTO = mapper.readValue(
"""
{
"name": "I'm variant B",
"bar": {
"size": 2
}
}
"""
)
println(a)
println(b)
assert(a.bar == null)
assert(a.bar?.size == 2)
}
Output:
returning null for value, no String-argument constructor/factory method to deserialize from String value ('uh oh random string instead of object')
FooDTO(name=I'm variant A, bar=null)
FooDTO(name=I'm variant B, bar=BarDTO(size=2))
Process finished with exit code 0

Related

I have an Pojo Kotlin Object which contains list, child objects and sub child objects and I need to change the value of a field using field name

Example given classes as below
data class Book (
var name: String? = null,
var codes: List<Code>? = null,
var description: Description? = null
)
data class Code(
var value: String? = null
var date: String? = null
)
data class Description(
var notes: String? = null
)
I am looking a util where I can change the value of the given field name.
For example: I need to change the date value of Code class using field name "date". I am looking at generic util class which we can use to change the value at any given child class or list or map....which matches the give string name
I do not know of any existing utils for that purpose.
A very simple method you could use would be a Jackson object mapper:
inline fun <reified T: Any> setProperty(c: T, propName: String, value: Any?): T {
val map = jacksonObjectMapper().convertValue<Map<String, Any?>>(c).toMutableMap()
map[propName] = value
return jacksonObjectMapper().convertValue(map)
}
Then you can for instance write the following to set the date value of a Code object:
val code = Code("x", "y")
val resultCode = setProperty(code, "date", "z")
But please note that this has very bad performance since it converts the data class to json, then to a map, then again to json, and finally back into the data class. It is also not very good because it creates a new object each time you change one property.
A bit better would be to use Kotlin reflection like this:
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
fun <T : Any> setProp(c: T, propName: String, value: Any?) {
val prop = getMutableProperty(c, propName)
val type = prop.setter.parameters[1].type
if (value == null && !type.isMarkedNullable) error("property $propName is not nullable")
prop.setter.call(c, value)
}
fun <T: Any> getMutableProperty(c: T, propName: String): KMutableProperty<*> {
val prop = c::class.memberProperties.find { it.name == propName } ?: error("no property $propName")
if (prop !is KMutableProperty<*>) error("property $propName is not mutable")
return prop
}
This can be used to change the property of a data class without creating a new element on each changed property which would look like this:
val code = Code("x", "y")
setProp(code, "date", "myval")
println(code) // now prints Code(value=x, date=myval)

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.

General Gson Serialization of API types

I am working with an API that can have different types for it's attributes
The attributes can either be Ids or Objects
I want to build a generalized type that handles this for me with Gson serialization.
Example:
"platforms": [
6
]
"platforms": [
{
"id": 6,
"name": "PC (Microsoft Windows)",
"slug": "win",
"url": "https://www.igdb.com/platforms/win",
"created_at": 1297639288000,
"updated_at": 1470063140518,
"website": "http://windows.microsoft.com/",
"alternative_name": "mswin"
}
]
I am working with Kotlin and have started building my Generalizable class
data class ObjectType<T>(
var Id: Long? = null,
var expand: T? = null
)
I am currently stuck in constructing my JsonDeserializer, as it needs a return of type T which in my case can be both an Int or an Object. I have tried to replace the T with ObjectType which works 'better' but cannot handle the cases when the JSON is an array.
I am currently trying to make it work with just the Generalized Type T as I can set the type as List> instead.
Current Implementation:
class ObjectDeserializer<T> : JsonDeserializer<T> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
if (json != null) {
if (json.isJsonArray) {
val struct: T = Gson().fromJson(json, T::class.java) as T
return struct
} else {
val id = Gson().fromJson(json, Long::class.java)
//return ObjectType(id, null)
}
}
return T as T
}
}
I would love some input on how to solve this.
Your implementation has some issues and inconsistencies. First you have to make sure to deserialize ObjectType<T>. Thus you have to declare the class as:
class ObjectDeserializer<T> : JsonDeserializer<ObjectType<T>>
It would also be easier to assume that all parameters are non-null:
override fun deserialize(json: JsonElement, typeOfT: Type,
context: JsonDeserializationContext): ObjectType<T>
Now you can use typeOfT which is actually the type of T in JsonDeserializer, not in ObjectDeserializer. Therefore it's the type of ObjectType<T> you need to deserialize. To move to the next step you need to find the actual type of T:
val objectTypeType = typeOfT as ParameterizedType
val actualTypeOfT = objectTypeType.getActualTypeArguments()[0]
As the next step you need to figure out the contents of json. In your case you won't ever find an array, but an object or a long:
return if (json.isJsonObject()) {
val struct: T = context.deserialize(json, actualTypeOfT)
ObjectType(expand = struct)
} else {
val id = json.getAsLong()
ObjectType(Id = id)
}
Here you return the ObjectType instances without any error handling, which you might need to add as well.
Then you should provide this deserializer to Gson by:
registerTypeAdapter(ObjectType::class.java, ObjectDeserializer<Any>())
Whenever Gson needs to deserialize an ObjectType<TheType>, it finds the instance of ObjectDeserializer and provides ObjectType<TheType> as typeOfT to deserialize.

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.

scala.Some cannot be cast to custom object

I am having below code. I have started learning scala so might be there would be better approach to do these stuffs but I want to learn every bit of it. Please bear with me if code looks so naive.
class ColaProduct() extends Product{
override def productName = "Cola"
override def productDetails = "Chilled Cola"
override def toString(): String = super.toString()
}
class MilkProduct() extends Product{
override def productName = "Milk"
override def productDetails = "Healthy Milk"
override def toString(): String = super.toString()
}
trait Machine {
private val productMap = scala.collection.mutable.Map[String, Product]()
def addProduct(product: Product): Unit ={
productMap += product.productName.toString -> product
}
def checkAvl(name :String): Product ={
if(productMap contains(name)){
return productMap.get(name).asInstanceOf[Product]
} else null
}
def process(name :String)
}
class VendingMachineImpl() extends Machine{
override def process(name : String): Unit ={
val product = checkAvl(name)
if(null !=product){
print("Got you :"+product.toString())
}
}
}
trait Product {
private val defaultString: String = "Default"
def productName = defaultString
def productDetails = defaultString
override def toString(): String = {
return productName + " || " + productDetails
}
}
def main(args : Array[String]): Unit ={
val vendingMachineImpl = new VendingMachineImpl()
vendingMachineImpl.addProduct(new ColaProduct)
vendingMachineImpl.addProduct(new MilkProduct)
vendingMachineImpl.process("Cola")
}
Exception:
Exception in thread "main" java.lang.ClassCastException: scala.Some cannot be cast to Product
at vendingMachine$Machine$class.checkAvl(vendingMachine.scala:27)
at vendingMachine$vendingMachineImpl.checkAvl(vendingMachine.scala:33)
at vendingMachine$vendingMachineImpl.process(vendingMachine.scala:35)
at vendingMachine$.main(vendingMachine.scala:47)
What I believed is once I define the map with specified type I don't have to match once retrieve the values from map. Is this understanding correct if not please let me know what is going wrong here.
If we look at the scaladoc for scala.collection.mutable.Map.get(), the method signature is described as:
abstract def get(key: A): Option[B]
The method returns a value of type Option; if you try to cast it to some unrelated type, you will get a ClassCastException, as for any other incompatible types. If you want a product, you need to:
check that the Option isn't empty
unwrap the Product contained inside
Here's one way of doing it (without changing the rest of the code):
trait Machine {
// Notes:
// - no need to use 'return' keyword
// - It's good practice in Scala not to return null: use Option for optional values
def checkAvl(name :String): Option[Product] =
productMap.get(name)
}
class VendingMachineImpl() extends Machine{
override def process(name : String): Unit ={
val product = checkAvl(name)
// Think of Option as a collection containing 0 or 1 element: you can use
// foreach, map, etc.
// Also, string interpolation (with the s"" syntax) is cool. :-)
product.foreach(print(p => s"Got you : $p"))
}
}
Edit:
Also, in Scala, you can usually avoid explicit casts thanks to pattern matching. For example, if you want to explicitely unwrap the Product from the Option[Product], you can use:
val opt: Option[Product] = ???
opt match {
// type-safe cast to type Some and deconstruction of the object (we get out the value
// wich was originally passed to the Some constructor) :
case Some(product) => print(s"Got you : $product")
// type-safe cast to type None :
case None => // do nothing
}
Edit 2:
you can also check out these two other methods for retrieving a value from a Map, depending on what you are trying to do:
val map: Map[String, String] = Map("k1" -> "v1", "k2" -> "v2")
// getOrElse (using a default value)
map.getOrElse("k1", "default") // returns "v1"
map.getOrElse("foobar", "default") // returns "default"
// apply (implementation-dependent, but fails fast by default)
map("k1") // returns "v1"
map("foobar") // throws a NoSuchElementException (it can be different for other Map
// implementations, but it's the default behavior)
try productMap.get(name).get().asInstanceOf[Product]
or just productMap(name).asInstanceOf[Product]

Categories

Resources