How to serialize request object without field name in Spring Boot? - java

I have simple REST Controller (in Kotlin) method with #RequestBody object.
#PostMappging(...)
fun postTestFunction(#RequestBody data: ExampleObject) {
...
}
Where ExampleObject is:
class ExampleObject(
#JsonProperty("paramOne")
val paramOne: Map<String, Int>,
#JsonProperty("paramTwo")
val paramTwo: String
)
That request can be send with next body:
{
"paramOne": {
"test": 1 // Here I can write any number of Map<String, Int>
},
"paramTwo": "test"
}
But I need another request body something like this:
{
"test": 1, // I need any number of Map<String, Int> without field name "paramOne"
"paramTwo": "test"
}
What should I do to send request body with Map field (String, Int) but without field name?

I found solution. It's need to use #JsonAnySetter annotation.
I changed ExampleObject to:
class ExampleObject(
#JsonProperty("paramTwo")
val paramTwo: String
) {
#JsonProperty("paramOne")
val paramOne = MutableMap<String, Int> = mutableMapOf()
#JsonAnySetter
fun setParamOne(key: String, value: Int) {
paramOne[key] = value
}
}
And now I can send request body the way I wanted. All fields that don't match with declared #JsonProperty annotation will be assigned to property with #JsonAnySetter method.
{
"paramTwo": "data",
"testStringOne": 1,
"testStringTwo": 2,
...
}

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)

Jackson Deserializer default to null on failure

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

ObjectMapper - Desserialize flattened object

I have the following class that uses internally a HashMap:
open class I18n<T> {
var i18n: MutableMap<LanguageEnum, T?> = mutableMapOf()
#JsonAnyGetter get
fun add(lang: LanguageEnum, value: T?) {
i18n[lang] = value
}
// ...
}
Thanks to the #JsonAnyGetter annotation, when I serialize this into Json I have the following format:
{
"pt": "Texto exemplo",
"en": "Example text"
}
instead of
{
i18n: {
"pt": "Texto exemplo",
"en": "Example text"
}
}
Now I need to make the way back. I have a HashMap containing the languages key and I need to desserialize it into my I18n object.
The caveat here is that I'm doing this among a lot of reflections and abstractions and it would be really nice if it could work like this:
// Here I am going through the fields of a given POJO.
// One of those fields is a I18n type.
// My model variable is a Map containing the same keys as my POJO field's name, so I'm basically trying to map them all
for (f in fields) {
if (model.containsKey(f.name)) {
// when f.type is I18n, value is a HashMap<string, string>
val value = model[f.name]
f.isAccessible = true
// when f.type is I18n.class, the field is set to an empty instance of I18n because it could not desserialize
f.set(dto, mapper.convertValue(value, f.type))
f.isAccessible = false
}
}
I would not like to do things like:
if (f.type === I18n.class) {
// Special treatment
}
Any ideas? Thanks in advance.

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.

Categories

Resources