From 1d3781861351cbbce9dafb93f3c0c6e11bbcb725 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 14:59:30 +0900 Subject: [PATCH 01/25] =?UTF-8?q?=E5=BA=8F=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b06d9dc..42ffebf 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,12 @@ KMapper ==== -This is a Mapper Libraly like a `ModelMapper` for `Kotlin`. -You can call `KFunction`(e.g. `method reference`) from `Object`. +`KMapper` is a mapper library for `Kotlin`, which provides the following features. -```kotlin -// before -val dst = Dst( - param1 = src.param1, - param2 = src.param2, - param3 = src.param3, - param4 = src.param4, - param5 = src.param5, - ... -) +- `Bean mapping` with `Objects`, `Map`, and `Pair` as sources +- Flexible and safe mapping based on function calls with reflection. +- Richer features and thus more flexible and labor-saving mapping. -// after -val newInstance = KMapper(::Dst).map(src) -``` ## How to use Published on JitPack. Please see [here](https://jitpack.io/#ProjectMapK/KMapper/) for the introduction method. From 5ec6b99bf5b9dd957b896b5641e12f2dd2226938 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:04:22 +0900 Subject: [PATCH 02/25] =?UTF-8?q?=E3=83=87=E3=83=A2=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 42ffebf..27671a3 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,40 @@ KMapper - Flexible and safe mapping based on function calls with reflection. - Richer features and thus more flexible and labor-saving mapping. +## Demo code +Here is a comparison between writing the mapping code by manually and using `KMapper`. + +If you write it manually, the more arguments you have, the more complicated the description will be. +However, by using `KMapper`, you can perform mapping without writing much code. + +Also, no external configuration file is required. + +```kotlin +// If you write manually. +val dst = Dst( + param1 = src.param1, + param2 = src.param2, + param3 = src.param3, + param4 = src.param4, + param5 = src.param5, + ... +) + +// If you use KMapper +val dst = KMapper(::Dst).map(src) +``` + +You can specify not only one source, but also multiple objects, `Pair`, `Map`, etc. + +```kotlin +val dst = KMapper(::Dst).map( + "param1" to "value of param1", + mapOf("param2" to 1, "param3" to 2L), + src1, + src2 +) +``` + ## How to use Published on JitPack. Please see [here](https://jitpack.io/#ProjectMapK/KMapper/) for the introduction method. From 758287900696f01e85f1985006d943974fbc265f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:10:05 +0900 Subject: [PATCH 03/25] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27671a3..96b8254 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,33 @@ val dst = KMapper(::Dst).map( ) ``` -## How to use -Published on JitPack. +## Installation +`KMapper` is published on JitPack. +You can use this library on maven, gradle and any other build tools. Please see [here](https://jitpack.io/#ProjectMapK/KMapper/) for the introduction method. +### Example on maven +**1. add repository reference for JitPack** + +```xml + + + jitpack.io + https://jitpack.io + + +``` + +**2. add dependency** + +```xml + + com.github.ProjectMapK + KMapper + Tag + +``` + ## Usages ### From multiple resources ```kotlin From ca51984bf2bd9ebbe70d427ab4036c8f7ecfbcef Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:10:24 +0900 Subject: [PATCH 04/25] =?UTF-8?q?=E5=8F=A4=E3=81=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 67 ------------------------------------------------------- 1 file changed, 67 deletions(-) diff --git a/README.md b/README.md index 96b8254..d58223e 100644 --- a/README.md +++ b/README.md @@ -77,70 +77,3 @@ Please see [here](https://jitpack.io/#ProjectMapK/KMapper/) for the introduction Tag ``` - -## Usages -### From multiple resources -```kotlin -class Src1(val arg1: String, val arg2: String) -val src2: Map = mapOf("arg3" to 1, "arg4" to 1.0) -val src3: Pair = "arg5" to null - -class Dst( - val arg1: String, - val arg2: String, - val arg3: Int, - val arg4: Double, - val arg5: String? -) - -val newInstance = KMapper(::Dst).map(src1, src2, src3) -``` - -### Set alias on map -#### for getter -```kotlin -class Src(@KGetterAlias("aliased") val str: String) - -class Dst(val aliased: String) - -val newInstance = KMapper(::Dst).map(src) -``` - -#### for parameter -```kotlin -class Src(val str: String) - -class Dst(@param:KPropertyAlias("str") private val _src: String) { - val src = _src.someArrangement -} - -val newInstance = KMapper(::Dst).map(src) -``` - -### Convert parameter name -```kotlin -val srcMap = mapOf("snake_case" to "SnakeCase") - -class Dst(val snakeCase: String) - -val dst: Dst = KMapper(::DataClass) { camelToSnake(it) }.map(src) -``` - -### Map param to another class - -```kotlin -class ConverterClass @KConverter constructor(val arg: String) { - companion object { - @KConverter - fun fromInt(arg: Int): ConverterClass { - return ConverterClass(arg.toString) - } - } -} - -class Src(val arg1: String, val arg2: Int) - -class Dst(val arg1: ConverterClass, val arg2: ConverterClass) - -val newInstance = KMapper(::Dst).map(src) -``` From 9cb61663db40fbf05ccd2be64c381ff8eba70b2e Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:21:06 +0900 Subject: [PATCH 05/25] =?UTF-8?q?=E5=8B=95=E4=BD=9C=E5=8E=9F=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d58223e..7b07846 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,15 @@ Please see [here](https://jitpack.io/#ProjectMapK/KMapper/) for the introduction Tag ``` + +## Principle of operation +The behavior of `KMapper` is as follows. + +1. Get the `KFunction` to be called. +2. Analyze the `KFunction` and determine what arguments are needed and how to deserialize them. +3. Get the value for each argument from inputs and deserialize it. and call the `KFunction`. + +`KMapper` performs the mapping by calling a `function`, so the result is a Subject to the constraints on the `argument` and `nullability`. +That is, there is no runtime error due to breaking the `null` safety of `Kotlin`(The `null` safety on type arguments may be broken due to problems on the `Kotlin` side). + +Also, it supports the default arguments which are peculiar to `Kotlin`. From 64ffdde21b5588a768e2b4127881ccf9b715f79b Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:35:23 +0900 Subject: [PATCH 06/25] =?UTF-8?q?=E3=83=9E=E3=83=83=E3=83=91=E3=83=BC?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=AE=E7=A8=AE=E9=A1=9E=E3=81=AB?= =?UTF-8?q?=E3=81=A4=E3=81=84=E3=81=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 7b07846..453076e 100644 --- a/README.md +++ b/README.md @@ -89,3 +89,25 @@ The behavior of `KMapper` is as follows. That is, there is no runtime error due to breaking the `null` safety of `Kotlin`(The `null` safety on type arguments may be broken due to problems on the `Kotlin` side). Also, it supports the default arguments which are peculiar to `Kotlin`. + +## Types of mapper classes +The project offers three types of mapper classes. + +- `KMapper` +- `PlainKMapper` +- `BoundKMapper` + +Here is a summary of the features and advantages of each. +Also, the common features are explained using `KMapper` as an example. + +### KMapper +The `KMapper` is a basic mapper class for this project. +It is suitable for using the same instance of the class, since it is cached internally to speed up the mapping process. + +### PlainKMapper +`PlainKMapper` is a mapper class from `KMapper` without caching. +Although the performance is not as good as `KMapper` in case of multiple mappings, it is suitable for use as a disposable mapper because there is no overhead of cache processing. + +### BoundKMapper +`BoundKMapper` is a mapping class for the case where only one source class is available. +It is faster than `KMapper`. From e0622b16423113154327de4e0718bf16692183bb Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:48:19 +0900 Subject: [PATCH 07/25] =?UTF-8?q?KMapper=E3=81=AE=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E5=8C=96=EF=BC=88method=20reference=E3=81=BE=E3=81=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 453076e..1f2e4e1 100644 --- a/README.md +++ b/README.md @@ -111,3 +111,33 @@ Although the performance is not as good as `KMapper` in case of multiple mapping ### BoundKMapper `BoundKMapper` is a mapping class for the case where only one source class is available. It is faster than `KMapper`. + +## Initialization +`KMapper` can be initialized from `method reference(KFunction)` to be called or the `KClass` to be mapped. + +The following is a summary of each initialization. +However, some of the initialization of `BoundKMapper` are shown as examples simplified by a dummy constructor. + +### Initialize from method reference(KFunction) +When the `primary constructor` is the target of a call, you can initialize it as follows. + +```kotlin +data class Dst( + foo: String, + bar: String, + baz: Int?, + + ... + +) + +// Get constructor reference +val dstConstructor: KFunction = ::Dst + +// KMapper +val kMapper: KMapper = KMapper(dstConstructor) +// PlainKMapper +val plainMapper: PlainKMapper = PlainKMapper(dstConstructor) +// BoundKMapper +val boundKMapper: BoundKMapper = BoundKMapper(dstConstructor) +``` From 3d72900d8bb0b322290631c4e4014e4c97222f4f Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 15:53:49 +0900 Subject: [PATCH 08/25] =?UTF-8?q?KClass=E3=81=8B=E3=82=89=E3=81=AE?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 1f2e4e1..0c3cc94 100644 --- a/README.md +++ b/README.md @@ -141,3 +141,29 @@ val plainMapper: PlainKMapper = PlainKMapper(dstConstructor) // BoundKMapper val boundKMapper: BoundKMapper = BoundKMapper(dstConstructor) ``` + +### Initialize from KClass +The `KMapper` can also be initialized from the `KClass`. +By default, the `primary constructor` is the target of the call. + +```kotlin +data class Dst(...) + +// KMapper +val kMapper: KMapper = KMapper(Dst::class) +// PlainKMapper +val plainMapper: PlainKMapper = PlainKMapper(Dst::class) +// BoundKMapper +val boundKMapper: BoundKMapper = BoundKMapper(Dst::class, Src::class) +``` + +By using a `dummy constructor` and omitting `generics`, you can also write as follows. + +```kotlin +// KMapper +val kMapper: KMapper = KMapper() +// PlainKMapper +val plainMapper: PlainKMapper = PlainKMapper() +// BoundKMapper +val boundKMapper: BoundKMapper = BoundKMapper() +``` From 4d21cb38c2b37728fae56b687cff4a1794885fa0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 16:10:19 +0900 Subject: [PATCH 09/25] =?UTF-8?q?KConstructor=E3=82=A2=E3=83=8E=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AB=E3=82=88=E3=82=8B?= =?UTF-8?q?=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97=E5=AF=BE=E8=B1=A1=E6=8C=87?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 0c3cc94..11b71b3 100644 --- a/README.md +++ b/README.md @@ -167,3 +167,32 @@ val plainMapper: PlainKMapper = PlainKMapper() // BoundKMapper val boundKMapper: BoundKMapper = BoundKMapper() ``` + +### Specifying the target of a call by KConstructor annotation +When initializing from the `KClass`, all mapper classes can specify the function to be called by the `KConstructor` annotation. + +In the following example, the `secondary constructor` is called. + +```kotlin +data class Dst(...) { + @KConstructor + constructor(...) : this(...) +} + +val mapper: KMapper = KMapper(Dst::class) +``` + +Similarly, the following example calls the factory method. + +```kotlin +data class Dst(...) { + companion object { + @KConstructor + fun factory(...): Dst { + ... + } + } +} + +val mapper: KMapper = KMapper(Dst::class) +``` From 5f4a91de64695ceb164c497c8c6c9be8c15dc303 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 16:29:04 +0900 Subject: [PATCH 10/25] =?UTF-8?q?=E3=83=8D=E3=82=B9=E3=83=88=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=83=9E=E3=83=83=E3=83=94=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 11b71b3..c09b7b3 100644 --- a/README.md +++ b/README.md @@ -196,3 +196,39 @@ data class Dst(...) { val mapper: KMapper = KMapper(Dst::class) ``` + +## Detailed usage + +### Converting values during mapping +In mapping, you may want to convert one input type to another. +The `KMapper` provides a rich set of conversion features for such a situation. + +However, this conversion can be performed under the following conditions. + +- Input is not `null`. + - If `null` is involved, it is recommended to combine the `KParameterRequireNonNull` annotation with the default argument. +- Input cannot be assigned directly to an argument. + +#### Conversions available by default +Some of the conversion features are available without any special description. + +##### 1-to-1 conversion (nested mapping) +If you can't use arguments as they are and no other transformation is possible, `KMapper` tries to do 1-to-1 mapping using the mapping class. +This allows you to perform the following nested mappings by default. + +```kotlin +data class InnerDst(val foo: Int, val bar: Int) +data class Dst(val param: InnerDst) + +data class InnerSrc(val foo: Int, val bar: Int) +data class Src(val param: InnerSrc) + +val src = Src(InnerSrc(1, 2)) +val dst = KMapper(::Dst).map(src) + +println(dst.param) // -> InnerDst(foo=1, bar=2) +``` + +###### Specifies the function used for the nested mapping +Nested mapping is performed by initializing `BoundKMapper` from the class. +For this reason, you can specify the target of the call with the `KConstructor` annotation. From 849a2f8777181ab819ba7555870f0dc4d512162a Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 16:34:24 +0900 Subject: [PATCH 11/25] =?UTF-8?q?=E3=81=9D=E3=81=AE=E4=BB=96=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=8F=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index c09b7b3..b4a5b4e 100644 --- a/README.md +++ b/README.md @@ -232,3 +232,22 @@ println(dst.param) // -> InnerDst(foo=1, bar=2) ###### Specifies the function used for the nested mapping Nested mapping is performed by initializing `BoundKMapper` from the class. For this reason, you can specify the target of the call with the `KConstructor` annotation. + +##### Other conversions + +###### Conversion from String to Enum +If the input is a `String` and the argument is an `Enum`, an attempt is made to convert the input to an `Enum` with the corresponding `name`. + +```kotlin +enum class FizzBuzz { + Fizz, Buzz, FizzBuzz; +} + +data class Dst(val fizzBuzz: FizzBuzz) + +val dst = KMapper(::Dst).map("fizzBuzz" to "Fizz") +println(dst) // -> Dst(fizzBuzz=Fizz) +``` + +###### Conversion to String +If the argument is a `String`, the input is converted by `toString` method. From 7288b13d5f24fb0092ad824c10891f3fe18040e0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 16:40:08 +0900 Subject: [PATCH 12/25] =?UTF-8?q?KConverter=E3=82=A2=E3=83=8E=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=93=E3=81=A8=E3=81=AB=E3=82=88=E3=82=8B?= =?UTF-8?q?=E5=A4=89=E6=8F=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index b4a5b4e..15d1e70 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,42 @@ println(dst) // -> Dst(fizzBuzz=Fizz) ###### Conversion to String If the argument is a `String`, the input is converted by `toString` method. + +#### Specifying the conversion method using the KConverter annotation +If you create your own class and can be initialized from a single argument, you can use the `KConverter` annotation. +The `KConverter` annotation can be added to a `constructor` or a `factory method` defined in a `companion object`. + +```kotlin +// Annotate the primary constructor +data class FooId @KConverter constructor(val id: Int) +``` + +```kotlin +// Annotate the secondary constructor +data class FooId(val id: Int) { + @KConverter + constructor(id: String) : this(id.toInt()) +} +``` + +```kotlin +// Annotate the factory method +data class FooId(val id: Int) { + companion object { + @KConverter + fun of(id: String): FooId = FooId(id.toInt()) + } +} +``` + +```kotlin +// If the fooId is given a KConverter, Dst can do the mapping successfully without doing anything. +data class Dst( + fooId: FooId, + bar: String, + baz: Int?, + + ... + +) +``` From dc74e5f185e543589227a527e457d6b063ade3c3 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 16:46:03 +0900 Subject: [PATCH 13/25] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=83=8E=E3=83=86=E3=83=BC=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=92=E8=87=AA=E4=BD=9C=E3=81=97=E3=81=A6=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=8F=9B=EF=BC=88=E5=BA=8F=E6=96=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 15d1e70..60a50fb 100644 --- a/README.md +++ b/README.md @@ -290,3 +290,9 @@ data class Dst( ) ``` + +#### Conversion by creating your own custom deserialization annotations +If you cannot use `KConverter`, you can convert it by creating a custom conversion annotations and adding it to the parameter. + +Custom conversion annotation is made by defining a pair of `conversion annotation` and `converter`. +As an example, we will show how to create a `ZonedDateTimeConverter` that converts from `java.sql.Timestamp` or `java.time.Instant` to `ZonedDateTime` in the specified time zone. From a7e7dfe0a541f176c6b4174b329c818ef0564ab9 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 16:58:15 +0900 Subject: [PATCH 14/25] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=83=8E=E3=83=86=E3=83=BC=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=92=E5=AE=9A=E7=BE=A9=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 60a50fb..15207c9 100644 --- a/README.md +++ b/README.md @@ -296,3 +296,19 @@ If you cannot use `KConverter`, you can convert it by creating a custom conversi Custom conversion annotation is made by defining a pair of `conversion annotation` and `converter`. As an example, we will show how to create a `ZonedDateTimeConverter` that converts from `java.sql.Timestamp` or `java.time.Instant` to `ZonedDateTime` in the specified time zone. + +##### Create conversion annotation +You can define a conversion annotation by adding `@Target(AnnotationTarget.VALUE_PARAMETER)`, `KConvertBy` annotation, and several other annotations. + +The argument of the `KConvertBy` annotation passes the `KClass` of the converter described below. +This converter should be defined for each source type. + +Also, although this example defines an argument to the annotation, you can get the value of the annotation from the converter. + +```kotlin +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@KConvertBy([TimestampToZonedDateTimeConverter::class, InstantToZonedDateTimeConverter::class]) +annotation class ZonedDateTimeConverter(val zoneIdOf: String) +``` From d35aa68b0183ec9a8a288924485b6ba3a6e312d2 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:03:03 +0900 Subject: [PATCH 15/25] =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E3=83=BC=E3=82=92=E5=AE=9A=E7=BE=A9=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 15207c9..83b9443 100644 --- a/README.md +++ b/README.md @@ -312,3 +312,30 @@ Also, although this example defines an argument to the annotation, you can get t @KConvertBy([TimestampToZonedDateTimeConverter::class, InstantToZonedDateTimeConverter::class]) annotation class ZonedDateTimeConverter(val zoneIdOf: String) ``` + +##### Create converter +You can define `converter` by inheriting `AbstractKConverter`. +Generics `A`,`S`,`D` have the following meanings. + +- `A`: `conversion annotation` `Type`. +- `S`: Source `Type`. +- `D`: Destination `Type`. + +Below is an example of a converter that converts from `java.sql.Timestamp` to `ZonedDateTime`. + +```kotlin +class TimestampToZonedDateTimeConverter( + annotation: ZonedDateTimeConverter +) : AbstractKConverter(annotation) { + private val timeZone = ZoneId.of(annotation.zoneIdOf) + + override val srcClass: KClass = Timestamp::class + + override fun convert(source: Timestamp): ZonedDateTime = ZonedDateTime.of(source.toLocalDateTime(), timeZone) +} +``` + +The argument to the converter's `primary constructor` should only take a conversion annotation. +This is called when `KMapper` is initialized. + +As shown in the example, you can refer to the arguments defined in the annotation. From 96a3573ceb2316e0f999475a01e8fd29414ae82e Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:05:49 +0900 Subject: [PATCH 16/25] =?UTF-8?q?=E4=BB=98=E4=B8=8E=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index 83b9443..137a24c 100644 --- a/README.md +++ b/README.md @@ -339,3 +339,46 @@ The argument to the converter's `primary constructor` should only take a convers This is called when `KMapper` is initialized. As shown in the example, you can refer to the arguments defined in the annotation. + +##### Using custom conversion annotations +The conversion annotation and the converter defined so far are written together as follows. +`InstantToZonedDateTimeConverter` is a converter whose source is `java.time.Instant`. + +```kotlin +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@KConvertBy([TimestampToZonedDateTimeConverter::class, InstantToZonedDateTimeConverter::class]) +annotation class ZonedDateTimeConverter(val zoneIdOf: String) + +class TimestampToZonedDateTimeConverter( + annotation: ZonedDateTimeConverter +) : AbstractKConverter(annotation) { + private val timeZone = ZoneId.of(annotation.zoneIdOf) + + override val srcClass: KClass = Timestamp::class + + override fun convert(source: Timestamp): ZonedDateTime = ZonedDateTime.of(source.toLocalDateTime(), timeZone) +} + +class InstantToZonedDateTimeConverter( + annotation: ZonedDateTimeConverter +) : AbstractKConverter(annotation) { + private val timeZone = ZoneId.of(annotation.zoneIdOf) + + override val srcClass: KClass = Instant::class + + override fun convert(source: Instant): ZonedDateTime = ZonedDateTime.ofInstant(source, timeZone) +} +``` + +When this is given, it becomes as follows. + +```kotlin +data class Dst( + @ZonedDateTimeConverter("Asia/Tokyo") + val t1: ZonedDateTime, + @ZonedDateTimeConverter("-03:00") + val t2: ZonedDateTime +) +``` From f7f3ee8652127889c0acf19c9d840b7fdb24bac0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:14:02 +0900 Subject: [PATCH 17/25] =?UTF-8?q?=E8=A4=87=E6=95=B0=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=81=AE=E5=A4=89=E6=8F=9B=EF=BC=88=E5=BA=8F?= =?UTF-8?q?=E6=96=87=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 137a24c..92aac39 100644 --- a/README.md +++ b/README.md @@ -382,3 +382,27 @@ data class Dst( val t2: ZonedDateTime ) ``` + +#### Conversion from Multiple Arguments +The `KParameterFlatten` annotation allows you to perform a transformation that requires more than one argument. + +```kotlin +data class InnerDst(val fooFoo: Int, val barBar: String) +data class Dst(val bazBaz: InnerDst, val quxQux: LocalDateTime) +``` + +To specify a field name as a prefix, give it as follows. +The class specified with `KParameterFlatten` is initialized from the function or the `primary constructor` specified with the aforementioned `KConstructor` annotation. + +```kotlin +data class InnerDst(val fooFoo: Int, val barBar: String) +data class Dst( + @KParameterFlatten + val bazBaz: InnerDst, + val quxQux: LocalDateTime +) +data class Src(val bazBazFooBoo: Int, val bazBazBarBar: String, val quxQux: LocalDateTime) + +// required 3 arguments that bazBazFooFoo, bazBazBarBar, quxQux +val mapper = KMapper(::Dst) +``` From 3335cbf2e065e2cd2380a78b3982544a1a5df4f0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:29:20 +0900 Subject: [PATCH 18/25] =?UTF-8?q?KParameterFlatten=E3=82=A2=E3=83=8E?= =?UTF-8?q?=E3=83=86=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE=E3=82=AA?= =?UTF-8?q?=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 92aac39..a5be13e 100644 --- a/README.md +++ b/README.md @@ -406,3 +406,47 @@ data class Src(val bazBazFooBoo: Int, val bazBazBarBar: String, val quxQux: Loca // required 3 arguments that bazBazFooFoo, bazBazBarBar, quxQux val mapper = KMapper(::Dst) ``` + +##### KParameterFlatten annotation options +The `KParameterFlatten` annotation has two options for handling argument names of the nested classes. + +###### fieldNameToPrefix +By default, the `KParameterFlatten` annotation tries to find a match by prefixing the name of the argument with the name of the prefix. +If you don't want to prefix the argument names, you can set the `fieldNameToPrefix` option to `false`. + +```kotlin +data class InnerDst(val fooFoo: Int, val barBar: String) +data class Dst( + @KParameterFlatten(fieldNameToPrefix = false) + val bazBaz: InnerDst, + val quxQux: LocalDateTime +) + +// required 3 arguments that fooFoo, barBar, quxQux +val mapper = KMapper(::Dst) +``` + +If `fieldNameToPrefix = false` is specified, the `nameJoiner` option is ignored. + +###### nameJoiner +The `nameJoiner` specifies how to join argument names and argument names. +For example, if `Src` is `snake_case`, the following command is used. + +```kotlin +data class InnerDst(val fooFoo: Int, val barBar: String) +data class Dst( + @KParameterFlatten(nameJoiner = NameJoiner.Snake::class) + val bazBaz: InnerDst, + val quxQux: LocalDateTime +) + +// required 3 arguments that baz_baz_foo_foo, baz_baz_bar_bar, qux_qux +val mapper = KMapper(::Dst) { /* some naming transformation process */ } +``` + +By default, `camelCase` is specified, and `snake_case` and `kebab-case` are also supported. +You can also write your own by creating `object` which extends the `NameJoiner` class. + +##### Use with other conversion methods +The `KParameterFlatten` annotation also works with all the conversion methods introduced so far. +Also, the `KParameterFlatten` annotation can be used in any number of layers of nested objects. From 04d45e10e5d950c5b1ab53e559d151a9c36dd649 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:42:50 +0900 Subject: [PATCH 19/25] =?UTF-8?q?=E5=BC=95=E6=95=B0=E5=90=8D=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=8F=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index a5be13e..6ae661a 100644 --- a/README.md +++ b/README.md @@ -450,3 +450,60 @@ You can also write your own by creating `object` which extends the `NameJoiner` ##### Use with other conversion methods The `KParameterFlatten` annotation also works with all the conversion methods introduced so far. Also, the `KParameterFlatten` annotation can be used in any number of layers of nested objects. + +### Set the argument names and field names used for mapping +By default, `KMapper` searches the source for a field whose name corresponds to the argument name. +On the other hand, there are times when you want to use a different name for the argument name and the source. + +In order to deal with such a situation, `KMapper` provides some functions to set the argument name and field name used during mapping. + +#### Conversion of argument names +With `KMapper`, you can set the argument name conversion function at initialization. +It can handle situations where constant conversion is required, for example, the argument naming convention is camel case and the source naming convention is snake case. + +```kotlin +data class Dst( + fooFoo: String, + barBar: String, + bazBaz: Int? +) + +val mapper: KMapper = KMapper(::Dst) { fieldName: String -> + /* some naming transformation process */ +} + +// For example, by passing a conversion function to the snake case, the following input can be handled +val dst = mapper.map(mapOf( + "foo_foo" to "foo", + "bar_bar" to "bar", + "baz_baz" to 3 +)) +``` + +And, of course, any conversion process can be performed within the lambda. + +##### Propagation of the argument name conversion process +The argument name conversion process is also reflected in the nested mapping. +Also, the conversion is applied to the aliases specified with the `KParameterAlias` annotation described below. + +##### The actual conversion process +Although `KMapper` does not provide naming transformation, some of the most popular libraries in your project may also provide it. +Here is a sample code of `Jackson` and `Guava` that actually passes the "CamelCase -> SnakeCase" transformations. + +##### Jackson +```kotlin +import com.fasterxml.jackson.databind.PropertyNamingStrategy + +val parameterNameConverter: (String) -> String = PropertyNamingStrategy.SnakeCaseStrategy()::translate +val mapper: KMapper = KMapper(::Dst, parameterNameConverter) +``` + +##### Guava +```kotlin +import com.google.common.base.CaseFormat + +val parameterNameConverter: (String) -> String = { fieldName: String -> + CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fieldName) +} +val mapper: KMapper = KMapper(::Dst, parameterNameConverter) +``` From 9136068b162a04a7ba53cacb8598a70fa9d2efa2 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:45:34 +0900 Subject: [PATCH 20/25] =?UTF-8?q?=E3=82=B2=E3=83=83=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=A8=E3=82=A4=E3=83=AA=E3=82=A2=E3=82=B9=E3=82=92?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 6ae661a..a6abc03 100644 --- a/README.md +++ b/README.md @@ -507,3 +507,20 @@ val parameterNameConverter: (String) -> String = { fieldName: String -> } val mapper: KMapper = KMapper(::Dst, parameterNameConverter) ``` + +#### Set an alias for the getter +It is best to use the `KGetterAlias` annotation to rename the `_foo` field of the `Scr` class only at mapping time in the following code. + +```kotlin +data class Dst(val foo: Int) +data class Src(val _foo: Int) +``` + +The actual grant is as follows. + +```kotlin +data class Src( + @get:KGetterAlias("foo") + val _foo: Int +) +``` From 64ebe5fdf81c67009f1d6af86a1f81dff1fe68b6 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:46:51 +0900 Subject: [PATCH 21/25] =?UTF-8?q?=E5=BC=95=E6=95=B0=E5=90=8D=E3=81=AB?= =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=83=AA=E3=82=A2=E3=82=B9=E3=82=92=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index a6abc03..df3030a 100644 --- a/README.md +++ b/README.md @@ -524,3 +524,20 @@ data class Src( val _foo: Int ) ``` + +#### Set an alias to an argument name +It is best to use the `KParameterAlias` annotation if you want to change the name of the `_bar` field of the `Dst` class only at mapping time in the following code. + +```kotlin +data class Dst(val _bar: Int) +data class Src(val bar: Int) +``` + +The actual grant is as follows. + +```kotlin +data class Dst( + @KParameterAlias("bar") + val _bar: Int +) +``` From f0f5394a3a1852e817fd8d14685bb6890d465aab Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:53:01 +0900 Subject: [PATCH 22/25] =?UTF-8?q?=E5=88=B6=E5=BE=A1=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=82=92=E7=94=A8=E3=81=84=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index df3030a..c776919 100644 --- a/README.md +++ b/README.md @@ -541,3 +541,31 @@ data class Dst( val _bar: Int ) ``` + +### Other functions +#### Control and use default arguments +The `KMapper` uses the default argument if no argument is given. +Also, if an argument is given, you can control whether to use it or not. + +##### Always use the default arguments +If you want to force a default argument, you can use the `KUseDefaultArgument` annotation. + +```kotlin +class Foo( + ..., + @KUseDefaultArgument + val description: String = "" +) +``` + +##### Use default argument if input is null +The `KParameterRequireNonNull` annotation skips the input until a `non null` value is specified as an argument. +By using this, the default argument is used when all the corresponding contents are `null`. + +```kotlin +class Foo( + ..., + @KParameterRequireNonNull + val description: String = "" +) +``` From ef8d5a1da13a4e3de2ce3f1255b1f662872edef1 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 17:54:46 +0900 Subject: [PATCH 23/25] =?UTF-8?q?=E3=83=9E=E3=83=83=E3=83=94=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E6=99=82=E3=81=AB=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=89=E3=82=92=E7=84=A1=E8=A6=96=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index c776919..98a5fa8 100644 --- a/README.md +++ b/README.md @@ -569,3 +569,15 @@ class Foo( val description: String = "" ) ``` + +#### Ignore the field when mapping +If you want to ignore a field for mapping for some reason, you can use the `KGetterIgnore` annotation. +For example, if you enter the following class of `Src`, the `param1` field will not be read. + +```kotlin +data class Src( + @KGetterIgnore + val param1: Int, + val param2: Int +) +``` From 87285414eafdcd7ad30fef6cbe368ea223c504d8 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 18:05:02 +0900 Subject: [PATCH 24/25] =?UTF-8?q?=E5=BC=95=E6=95=B0=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E5=87=BA=E3=81=97=E3=81=AE=E5=AF=BE=E8=B1=A1=E3=83=BB=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E3=82=BB=E3=83=83=E3=83=88=E3=82=A2=E3=83=83?= =?UTF-8?q?=E3=83=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 98a5fa8..bef44ba 100644 --- a/README.md +++ b/README.md @@ -581,3 +581,33 @@ data class Src( val param2: Int ) ``` + +## Setting Up Arguments + +### Target for argument reading +The `KMapper` can read the `public` field of an object, or the properties of `Pair` and `Map`. + +### Setting Up Arguments +The `KMapper` performs the setup process if the value is not `null`. +In the setup process, first of all, `parameterClazz.isSuperclassOf(inputClazz)` is used to check if the input can be set as an argument or not, and if not, the conversion described later is performed and the result is used as an argument. + +If the value is `null`, the `KParameterRequireNonNull` annotation is checked, and if it is set, the setup process is skipped, otherwise `null` is used as the argument. + +If the `KUseDefaultArgument` annotation is set or all inputs are skipped by the `KParameterRequireNonNull` annotation, the default argument is used. +If the default argument is not available at this time, a runtime error occurs. + +#### Conversion of arguments +`KMapper` performs conversion and checking in the following order. + +**1. Checking the specification of the conversion process by annotation** +First of all, it checks for conversions specified by the `KConvertBy` and `KConverter` annotations for the class of the input. + +**2. Confirmation of conversion to Enum** +If the input is a `String` and the argument is an `Enum`, the function tries to convert the input to an `Enum` with the corresponding `name`. + +**3. Confirmation of conversion to string** +If the argument is `String`, the input will be `toString`. + +**4. Conversion using the mapper class** +If the transformation does not meet the criteria so far, a mapping process is performed using a mapper class. +For this mapping process, `PlainKMapper` is used for `PlainKMapper`, and `BoundKMapper` is used for others. From f9f69c779b08eedf17aeeb4132e0e9269451014c Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sat, 1 Aug 2020 18:08:26 +0900 Subject: [PATCH 25/25] =?UTF-8?q?=E5=85=A5=E5=8A=9B=E3=81=AE=E5=84=AA?= =?UTF-8?q?=E5=85=88=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index bef44ba..20831e3 100644 --- a/README.md +++ b/README.md @@ -611,3 +611,15 @@ If the argument is `String`, the input will be `toString`. **4. Conversion using the mapper class** If the transformation does not meet the criteria so far, a mapping process is performed using a mapper class. For this mapping process, `PlainKMapper` is used for `PlainKMapper`, and `BoundKMapper` is used for others. + +### Input priority +The `KMapper` basically gives priority to the first available argument. +For example, in the following example, since `param1` is given first as `value1`, the next input `param1" to "value2"` is ignored. + +```kotlin +val mapper: KMapper = ... + +val dst = mapper.map("param1" to "value1", "param1" to "value2") +``` + +However, if `null` is specified as an input for an argument with a `KParameterRequireNonNull` annotation, it is ignored and the later argument takes precedence.