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)
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
I am making a call from service A which is in Kotlin to service B which is in Java. It return me an object which contains multiple fields. One of the fields returned in the Java object is an enum. In my kotlin code I have defined a DTO which maps the returned response to kotlin. I need to map this enum to a string value in kotlin.
DTO in Java:
public class PersonDTO
{
private Long id;
private String name;
private CountryCode countryCode;
}
The CountryCode is an enum.
Data class in Kotlin:
data class PersonDTO(
val id: Long? = null,
val name: String? = null,
val countryCode: String? = null //How to map the enum to string here..???
)
Any help would be appreciated.
Answer to the edited question: How to map a Java enum to a String?
you can call name() or toString() on an enum to get a String representation of it.
name() cannot be overwritten and always returns the textual representation of the value defined in the code, while toString() can be overwritten, so it might be depending on your use case what to use. Because of the fact that name() cannot be overwritten I prefer to always use name() wich can have less side effects or unexpected behavior when working with libraries which are not under your control.
Original Answer:
1 you don't have to do this. You can use the same Java class also in Kotlin code.
2 You could just reuse the enum, like in option 1) you can reuse the Java enum in Kotlin code:
data class PersonDTO(
val id: Long? = null,
val name: String? = null,
val countryCode: CountryCode
)
3 You can write a Kotlin enum with a mapping function to create the matching instance of the enum:
enum class KotlinCountryCode {
EXAMPLE;
fun fromJavaCountryCode(input: CountryCode): KotlinCountryCode? {
if (input.name() == EXAMPLE.name) {
return EXAMPLE
}
return null
}
}
The problem is when I created the instance of my Model class and pass the non-nullable variable to the constructor, the compiler shows the error Type-mismatch.
I have fixed the type-mismatch error by making model class variable as nullable
But I couldn't understand the error shown by the compiler.
Model class
class SharedPreferenceEntry (val name:String, val dateOfBirth:Calendar, val email:String)
Helper class SharedPreferencesHelper, where I created an instance of Model class and return that instance from function
fun getPersonalInfo(): SharedPreferenceEntry { // Get data from the SharedPreferences.
val name = mSharedPreferences.getString(KEY_NAME, "")
val dobMillis =
mSharedPreferences.getLong(KEY_DOB, Calendar.getInstance().getTimeInMillis())
val dateOfBirth: Calendar = Calendar.getInstance()
dateOfBirth.setTimeInMillis(dobMillis)
val email = mSharedPreferences.getString(KEY_EMAIL, "")
// Create and fill a SharedPreferenceEntry model object.
return SharedPreferenceEntry(name, dateOfBirth, email)
}
As #sonnet commented, the use of mSharedPreferences.getString(...) will return null if the key is mapped to null. To ensure, the value of mSharedPreferences.getString(...) is non-null, change it to mSharedPreferences.getString(...) ?: "".
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.