Kotlin, mapstruct and generated DTO error "does not have an accessible parameterless constructor." - kotlin

I am trying to use mapstruct to convert my complex dtos on my kotlin project.
mapstruct : 1.3.1.final
kotlin: 1.3.71
openapi generator: 4.2.3
For example, i want to convert from a simple object to TestObjectDTO
#Mapping(source = "mydescription", target = "description")
fun convertToDto(dto: TestObject): TestObjectDTO
I use OpenApi to generate my DTO :
yaml
components:
schemas:
TestObject:
title: TestObject
description: ''
type: object
properties:
mode:
type: string
description:
type: string
required:
- mode
- description
generated DTO
/**
*
* #param mode
* #param description
*/
data class TestObjectDTO (
#get:NotNull
#JsonProperty("mode") var mode: kotlin.String,
#get:NotNull
#JsonProperty("description") var description: kotlin.String
) {
}
A always have an error, because my constructor does not permit parameterless.
Did you have any idea how to fix this?

You can instantiate the DTO class manually using #ObjectFactory. The problem is that TestObjectDTO does not accept nulls, so you will need to use dummy values, which is not that pretty:
#Mapper
interface TestObjectMapper {
#ObjectFactory
fun createDto() = TestObjectDto("", "")
#Mapping(source = "mydescription", target = "description")
fun convertToDto(dto: TestObject): TestObjectDto
}

1.3.1.Final does not support using constructors to create your objects. You will have to define a parameter less constructor as the error message says.
However, you can try 1.4.0.Beta3 that adde support for mapping using constructors. And this works with Kotlin data classes

In Java
A possible solution for this is to annotate the dto class with #NoArgsConstructor, for example:
#NoArgsConstructor
public class MyClass {
private String myString;
private Int myInt;
}

Related

How to read kotlin annotation

I have annotation classes
annotation class Endpoint(val name: String)
#Target(AnnotationTarget.TYPE)
annotation class JsonObjectKey(val name: String)
These are used like so
#Endpoint("my-endpoint")
fun myEndpoint(): #JsonObjectKey("key") String {
return "value"
}
With a method: java.lang.reflect.Method object representing myEndpoint,
I am able to access the Endpoint-Annotation, but I fail to access the JsonObjectKey-Annotatation.
method.annotations only contains Endpoint
method.annotatedReturnType.annotations is empty
How to read JsonObjectKey (key in this scenario)?
Intention is to generate JsonObject {"key": "value"}
Use the Kotlin reflection API!
For example, this works:
fun main() {
println(::myEndpoint.returnType.annotations)
}
#Target(AnnotationTarget.TYPE)
annotation class JsonObjectKey(val name: String)
fun myEndpoint(): #JsonObjectKey("key") String {
return "value"
}
This outputs:
[#JsonObjectKey(name=key)]
If you only have a java.lang.reflect.Method for some reason, you can convert it to a KFunction using kotlinFunction:
println(someJavaMethod?.kotlinFunction?.returnType?.annotations)
It appears that annotations annotated in this position in Java:
public static #JsonObjectKey(name = "foo") String foo()
are very different from those annotated in this position in Kotlin:
fun foo(): #JsonObjectKey("key") String = ""
These seem to be two different positions. Kotlin reflection cannot see the annotation on the Java return type, and Java reflection cannot see the annotation on the Kotlin return type.
Compare how the annotations appear in the class file. For the Java method, the annotation shows up in the RuntimeVisibleTypeAnnotations attribute:
RuntimeVisibleTypeAnnotations:
0: #23(#24=s#20): METHOD_RETURN
JsonObjectKey(
name="foo"
)
On the other hand, the Kotlin method instead has the annotation stored as part of the kotlin.Metadata annotation:
RuntimeVisibleAnnotations:
0: #86(#87=[I#88,I#89,I#90],#91=I#92,#93=I#94,#95=[s#96],#97=[s#5,s#98,s#76,s#98,s#99,s#100,s#101,s#102])
kotlin.Metadata(
mv=[1,6,0]
k=2
xi=48
d1=["..."]
d2=["main","","myEndpoint","","LJsonObjectKey;","name","key","myproject"]
)

Ktor - create List from Json file

i am getting error - This class does not have constructor at object : TypeToken<List<Todo>>() + object is not abstract and does not implement object member
data class Todo(
val identifier: Long ,
val name: String ,
val description: String
)
class DefaultData {
private lateinit var myService: MyService
#PostConstruct
fun initializeDefault() {
val fileContent = this::class.java.classLoader.getResource("example.json").readText()
val todos: List<Todo> = Gson().fromJson(fileContent, object : TypeToken<List<Todo>>() {}.type)
myService.createTodoFromJsontodos
}
}
how can I fix this?
Objective is : To be able to create an endpoint that can get data from json file via service
Is there is a full fledged example
Also how to create interfaces in Ktor? As I want to use Dependency Inversion to enable retrieving data from different sources
Kotlin has built-in util similar to TypeToken, so I suggest using it instead:
Gson().fromJson(fileContent, typeOf<List<Todo>>().javaType)
You will need to add a dependency to kotlin-reflect. typeOf() function is marked as experimental, but I use it for some time already and never had any problems with it.
Also, you said in your comment that this is a starter project. If you don't have any existing code already then I suggest to use kotlinx-serialization instead of Gson. It is a de facto standard in Kotlin.
You can easily take advantage of kotlinx-serialization.
Steps:
Add the kotlin serialization plugin in your build.gradle file
kotlin("plugin.serialization") version "1.5.20"
plugins {
application
java
id("org.jetbrains.kotlin.jvm") version "1.5.21"
kotlin("plugin.serialization") version "1.5.20"
}
Add the dependecy for serialization library
dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2")
}
Decode your json string to corresponding object using Json decode method
val JSON = Json {isLenient = true}
val mytodos = JSON.decodeFromString(message) as List<Todo>

Can you define alternative shorthands for Kotlin Annotations?

I am using annotations and reflection to create a parser for some custom made files used in the project I work with
I have this annotation that will be used to annotate most data class constructor parameters
annotation class Element(val name: String = "",val type: ElementType = ElementType.Value)
the enum ElementType has these values
enum class XElementType {
Value,
Attribute,
Ignore
}
is there a way to create a shorthand or alternate so that instead of using
#Element(type=ElementType.Ignore)
val ignoredVariable:String
I can use something like
#IgnoreElement
val ignoredVariable:String
which will resolve to Element("",ElementType.Ignore) ?

Error: "declares multiple JSON fields named..." when parsing classes with getter/setter values defined in interfaces

Consider the following classes/interfaces:
interface ExampleKotlinInterface {
val name: String
}
interface ExampleKotlinInterfaceSubclass : ExampleKotlinInterface {
override var name: String
}
abstract class ExampleKotlinImpl(#SerializedName("name") override val name: String = "zach") : ExampleKotlinInterface
class ExampleKotlinImplSubclass(override var name: String) : ExampleKotlinImpl(name), ExampleKotlinInterfaceSubclass
where the first interface defines a value name and the second interface extends this, but exposes the name value as a var. I am unable to parse the ExampleKotlinImplSubclass with Gson because of the following error:
java.lang.IllegalArgumentException: class com.example.kotlingetterinterfaceexample.ExampleKotlinImplSubclass declares multiple JSON fields named name
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
at com.google.gson.Gson.getAdapter(Gson.java:458)
at com.google.gson.Gson.fromJson(Gson.java:926)
at com.google.gson.Gson.fromJson(Gson.java:892)
at com.google.gson.Gson.fromJson(Gson.java:841)
at com.google.gson.Gson.fromJson(Gson.java:813)
at com.example.kotlingetterinterfaceexample.ExampleGsonParseTests.testParseObject(ExampleGsonParseTests.kt:13)
using this test code:
val json = "{ \"name\" : \"test\" }"
val result = Gson().fromJson(json, ExampleKotlinImplSubclass::class.java)
assertNotNull(result)
assertEquals("test", result.name)
The theory is that my superclass should not be allowed to change the value of name, but the subclass should be able to.
Any ideas on how to work around this?
Link to sample project showing this issue: https://github.com/ZOlbrys/kotlingetterinterfaceexample
See my Java based answer. GSON does not handle this kind of situation well. The only option I see is that you do not override fields. What you could do - if it is any help - create one more level in your interfaces, something like:
interface ExampleKotlinInterfaceTop {
fun getName(): String
...
}
interface ExampleKotlinInterface : ExampleKotlinInterfaceTop {
val name: String
...
}
interface ExampleKotlinInterfaceSubclass : ExampleKotlinInterfaceTop {
var name: String
...
}
I am not a Kotlin programmer so guess there is a lot to fix in my example code but as a principle of the possible solution.

spring data rest kotlin associations POST

I followed the tutorial http://www.baeldung.com/spring-data-rest-relationships.
I also observed that I can create the association directly by providing the link to the relationship.
curl -i -X POST -H "Content-Type:application/json" -d '{"name":"My Library"}' http://localhost:8080/libraries
curl -i -X POST -d '{"title":"Books", "library":"http://localhost:8080/libraries/1"}' -H "Content-Type:application/json" http://localhost:8080/books
This works fine in Java and also in Kotlin when using a regular class.
However, if I use a data class in Kotlin, I get the following error
2018-04-26 14:13:43.730 ERROR 79256 --- [nio-8080-exec-2] b.e.h.RestResponseEntityExceptionHandler : org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.baeldung.models.Library (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/libraries/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.baeldung.models.Library (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/libraries/1') at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 29] (through reference chain: com.baeldung.models.Book["library"])
I do have the relevant kotlin-spring, kotlin-jpa and kotlin-noarg plugins in my project.
Code is here https://github.com/vijaysl/spring-data-rest
Try adding #JsonCreator(mode = JsonCreator.Mode.DISABLED) annotation on primary constructor. No need to disable the com.fasterxml.jackson.module:jackson-module-kotlin.
Explanation:
Kotlin Jackson module implies your default constructor is the JSON creator (see KotlinValueInstantiator class).
Therefore, Spring Data REST does not apply its bean deserializer modifier (that is supposed to load a bean by URI) because bean properties mappings are not used for creator properties (constructor params).
KotlinValueInstantiator tries to deserialize constructor params using standard deserializers and instantiators and this leads to the error you mentioned.
Possible solution:
Since koltin-jpa module adds a default empty constructor for JPA, you can instruct Jackson not to use the JSON creator but the default empty constructor by explicitly disabling it.
Example:
#Entity
class Book #JsonCreator(mode = JsonCreator.Mode.DISABLED) constructor(
#ManyToMany
val libraries: ModifiableList<Library> = ArrayList(),
): AbstractPersistable<Long>(), Identifiable<Long>
Kotlin data classes are pretty strict. It's telling you, basically, it can't construct your POKO and it's listing some of the ways it tries. One of them is with a String constructor. Others are through private field manipulation (which is the way it's been done normally).
Data classes in kotlin, if they have fields declared as private val name:String translate to (in java) private final String name; It can't assign to a final field (which is dirty to try to assign to a private field, but impossible when it's final; the JVM won't allow it) and there are no getName() or setName() functions which can be used as another method of hydration.
Some options:
Declare your variables are var instead of val. private var name:String is java equiavalent to private String name which will use field based (dirty) hydration.
include a specific kotlin dependency for kotlin that fixes this issue: compile("com.fasterxml.jackson.module:jackson-module-kotlin") have a look at this project
example kotlin class that should work for you:
import org.springframework.hateoas.Identifiable
import java.time.LocalDate
import javax.persistence.*
import javax.validation.constraints.*
#Entity
data class Employee(#Pattern(regexp = "[A-Za-z0-9]+")
#Size(min = 6, max = 32)
val name: String,
#Email
#NotNull
val email: String?,
#PastOrPresent
val hireDate: LocalDate = LocalDate.now(),
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL])
val forms:List<Form> = listOf(),
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL])
val reports:List<Report> = listOf(),
#Id #GeneratedValue( strategy = GenerationType.IDENTITY) private val id: Long? = null): Identifiable<Long> {
override fun getId() = id
constructor(name:String): this(name,"$name#foo.com")
}
With kotlin all Ok.
Just replace "data class" to "class".
Jackson don't find empty constructor in "data class". And use other deserializator... not Uri....