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 have a simple scenario where an enum is being sent for storage and I want to pull it out. However, I cannot use either Ordinal nor Name strategy. The value being stored is a value inside each enum instance.
TLDR: Is there a way for me to register a mapper that does one thing on persistance and another on retrieval?
DISCLAIMER: I am not using posgres
// A simple enum
enum class N(val v: Int) {
A(10),B(15);
companion object {
val map = values(v: Int).associateBy { it.v }
fun from(v: Int) = map.getValue(v)
}
}
// A simple data class
data class A(val i: Int = 0, val n: N = N.A) {
interface Dao {
#SqlUpdate("INSERT INTO a (i) VALUE (?)")
#GetGeneratedKey
fun insert(i: Int): Int
#SqlQuery("SELECT i,n FROM a WHERE i = ?")
fun select(i: Int): A
}
}
// register my enum so that it stores the int value
jdbi.registerArgument(object : AbstractArgumentFactory<N>(Types.TINYINT) {
override fun build(value: N, config: ConfigRegistry): Argument {
return Argument { pos, stmt, _ -> stmt.setInt(pos, value.value) }
}
})
// test with Ordinal strategy
jdbi.configure(Enums::class.java) { it.setEnumStrategy(EnumStrategy.BY_ORDINAL) }
The simplest table possible
create table a(i int primary key auto_increment, n int);
I am trying to pull the data out but I get the following error:
Exception in thread "main" org.jdbi.v3.core.result.UnableToProduceResultException: no N value could be matched to the name 15
val dao = jdbi.onDemand(A.Dao::class.java)
val i = dao.insert(N.B)
printf(dao.select())
I know that I am using AbstractArgumentFactory which I think is the root of my problem. I don't think this helper is flexible to my needs.
Is there a way for me to register a mapper that does one thing on persistance and another on retrieval?
I think I found the solution. Much simpler than I expected
jdbi.registerColumnMapper(object : ColumnMapper<N> {
override fun map(r: ResultSet, columnNumber: Int, ctx: StatementContext): N {
val v = r.getInt(columnNumber)
return N.from(v)
}
})
After I dove into the Jdbi project and took a look at
org.jdbi.v3.core.mapper.EnumMapper
I made it work in a scratch file
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.
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.