102 votes

Meilleure façon de carte Kotlin les objets de données à des objets de données

Je veux convertir/carte quelques "données" de la classe d'objets similaires "données" des objets de la classe. Par exemple, les classes de formulaire web pour des classes pour les enregistrements de base de données.

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
)

J'utilise ModelMapper pour ces travaux en Java, mais il ne peut pas être utilisé parce que les classes de données sont définitives (ModelMapper crée CGLib procurations de lire la cartographie des définitions). Nous pouvons utiliser ModelMapper lorsque nous faisons de ces classes, des champs ouverts, mais nous devons mettre en œuvre des fonctionnalités de "données" de la classe manuellement. (cf. ModelMapper exemples: https://github.com/jhalterman/modelmapper/blob/master/examples/src/main/java/org/modelmapper/gettingstarted/GettingStartedExample.java)

Comment relier ces "données" des objets dans Kotlin?

Mise à jour: ModelMapper mappe automatiquement les champs qui ont le même nom (comme tel -> tel) sans cartographie des déclarations. Je veux le faire avec classe de données de Kotlin.

Mise à jour: Le but de chacune des classes dépend de quel genre d'application, mais ce sont probablement placé dans la couche différente d'une application.

Par exemple:

  • les données de la base de données (Base de données d'Entité) de données de formulaire HTML (Modèle/Vue Modèle)
  • API REST résultat aux données de la base de données

Ces classes sont similaires, mais ne sont pas les mêmes.

Je veux éviter normal appels de fonction pour ces raisons:

  • Il dépend de l'ordre des arguments. Une fonction de classe avec de nombreux domaines qui sont de même type (comme String) sera cassé facilement.
  • De nombreuses déclarations sont nesessary bien que la plupart des mappages peuvent être résolus avec la convention de nommage.

Bien sûr, une bibliothèque qui a une fonction similaire est prévu, mais les informations de la Kotlin fonctionnalité est également la bienvenue (comme la propagation dans ECMAScript).

87voto

mfulton26 Points 1609
  1. La plus simple (le meilleur?):

    fun PersonForm.toPersonRecord() = PersonRecord(
            name = "$firstName $lastName",
            age = age,
            tel = tel
    )
    
  2. Réflexion (pas grand rendement):

    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)
            }
        })
    }
    
  3. Mise en cache de la réflexion (bon rendement, mais pas aussi vite que n ° 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)
    
  4. Le stockage de Propriétés dans une Carte

    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")}"
    })
    

26voto

mate Points 155

Est-ce que vous cherchez?

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)           
    }
}

et puis:

val personRecord = PersonRecord.ModelMapper.from(personForm)

20voto

Utilisation MapStruct:

@Mapper
interface PersonConverter {

    @Mapping(source = "phoneNumber", target = "phone")
    fun convertToDto(person: Person) : PersonDto

    @InheritInverseConfiguration
    fun convertToModel(personDto: PersonDto) : Person

}

Utilisation:

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)

https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-kotlin

4voto

voddan Points 16

Voulez-vous vraiment dans une classe à part pour qui? Vous pouvez ajouter des propriétés à l'origine de classe de données:

data class PersonForm(
    val firstName: String,
    val lastName: String,
    val age: Int,
    val tel: String
) {
    val name = "${firstName} ${lastName}"
}

4voto

Tom Power Points 636

Cela fonctionne à l'aide de 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()
    )

avec pas nullable valeurs autorisées à être nulle, car Gson utilise le soleil.misc.Dangereux..

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X