Ich möchte einige "Daten" -Klassenobjekte in ähnliche "Daten" -Klassenobjekte konvertieren / zuordnen. Zum Beispiel Klassen für Webformulare zu Klassen für Datenbankeinträge.
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
)
Ich verwende ModelMapper für solche Arbeiten in Java, aber es kann nicht verwendet werden, da Datenklassen endgültig sind (ModelMapper erstellt CGLib-Proxys zum Lesen von Zuordnungsdefinitionen). Wir können ModelMapper verwenden, wenn wir diese Klassen / Felder öffnen, aber wir müssen Funktionen der Klasse "data" manuell implementieren. (Siehe ModelMapper-Beispiele: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java )
Wie ordne ich solche "Daten" -Objekte in Kotlin zu?
Update: ModelMapper ordnet automatisch gleichnamige Felder (wie tel -> tel) ohne Mapping-Deklarationen zu. Ich möchte es mit der Datenklasse von Kotlin machen.
Update: Der Zweck jeder Klasse hängt von der Art der Anwendung ab, diese werden jedoch wahrscheinlich in der verschiedenen Ebene einer Anwendung platziert.
Zum Beispiel:
- Daten aus der Datenbank (Datenbankentität) zu Daten für das HTML-Formular (Modell / Ansichtsmodell)
- REST-API-Ergebnis zu Daten für die Datenbank
Diese Klassen sind ähnlich, aber nicht gleich.
Ich möchte normale Funktionsaufrufe aus folgenden Gründen vermeiden:
- Dies hängt von der Reihenfolge der Argumente ab. Eine Funktion für eine Klasse mit vielen Feldern desselben Typs (wie String) kann leicht beschädigt werden.
- Viele Deklarationen sind notwendig, obwohl die meisten Zuordnungen mit Namenskonventionen aufgelöst werden können.
Natürlich ist eine Bibliothek mit ähnlichen Funktionen vorgesehen, aber auch Informationen über die Kotlin-Funktion sind willkommen (wie das Verbreiten in ECMAScript).
quelle
Antworten:
Am einfachsten (am besten?):
fun PersonForm.toPersonRecord() = PersonRecord( name = "$firstName $lastName", age = age, tel = tel )
Reflexion (keine großartige Leistung):
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) } }) }
Zwischengespeicherte Reflexion (okay Leistung, aber nicht so schnell wie # 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)
Speichern von Eigenschaften in einer Karte
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")}" })
quelle
@KotlinBuilder
ist eine schöne und schnelle Lösung. Siehe andere Antwort (wo ich@KotlinBuilder
Informationen hinzugefügt habe ).Suchen Sie das?
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) } }
und dann:
val personRecord = PersonRecord.ModelMapper.from(personForm)
quelle
Mit MapStruct kann kapt Klassen generieren, die das Mapping durchführen (ohne Reflexion).
Verwenden Sie 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) (...)
Verwenden:
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)
Bearbeiten:
Jetzt mit @KotlinBuilder, um das Problem constructor () zu vermeiden:
GitHub: Pozos Mapstruct-Kotlin
Kommentieren Sie Datenklassen mit
@KotlinBuilder
. Dadurch wird einePersonBuilder
Klasse erstellt, die MapStruct verwendet. Daher vermeiden wir, die Schnittstelle der Datenklasse mit einem Konstruktor () zu ruinieren.@KotlinBuilder data class Person( val firstName: String, val lastName: String, val age: Int, val tel: String )
Abhängigkeit:
// 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
quelle
constructor() : this(null, null, null, null)
. Bis das mapstruct-Team die richtige Kotlin-Unterstützung gefunden hat, würde ich diese vermeiden und die manuelle Konvertierung durchführen, wie in seiner ersten Lösung @ mfulton26 erwähnt.Willst du wirklich eine eigene Klasse dafür? Sie können der ursprünglichen Datenklasse Eigenschaften hinzufügen:
data class PersonForm( val firstName: String, val lastName: String, val age: Int, val tel: String ) { val name = "${firstName} ${lastName}" }
quelle
Dies funktioniert mit 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() )
mit nicht nullable Werte null sein darf , weil Gson verwendet sun.misc.Unsafe ..
quelle
Verwenden von 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) }
Verwendung
val form = PersonForm(/** ... **/) val record: PersonRecord = Mapper.convert(form)
Möglicherweise benötigen Sie einige Zuordnungsregeln, wenn sich die Feldnamen unterscheiden. Siehe Erste Schritte
PS: Verwenden Sie das
kotlin no-args
Plugin, um einen Standardkonstruktor ohne Argumente für Ihre Datenklassen zu verwendenquelle
Mit ModelMapper können Sie eine Kotlin-Datenklasse zuordnen. Die Schlüssel sind:
Veränderliches Mitglied, var statt val
quelle
Für ModelMapper können Sie das No- Arg -Compiler-Plugin von Kotlin verwenden , mit dem Sie eine Annotation erstellen können, die Ihre Datenklasse markiert, um einen synthetischen No-Arg-Konstruktor für Bibliotheken zu erhalten, die Reflection verwenden. Ihre Datenklasse muss
var
anstelle von verwendenval
.package com.example annotation class NoArg @NoArg data class MyData(var myDatum: String) mm.map(. . ., MyData::class.java)
und in build.gradle (siehe Dokumente für Maven):
buildscript { . . . dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } apply plugin: 'kotlin-noarg' noArg { annotation "com.example.NoArg" }
quelle