Kotlin data class and LocalDateTime - kotlin

I have Ticket class:
data class Ticket(
var contact_email : String? = null,
var date_opened : LocalDateTime? = null
)
but I get error during read from string:
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of java.time.LocalDateTime (no Creators, like
default construct, exist): no String-argument constructor/factory
method to deserialize from String value ('2017-11-13T06:40:00Z') at
[Source: UNKNOWN; line: -1, column: -1] (through reference chain:
rnd_classifier.model.Ticket["date_opened"])
I tried add annotations without success:
data class Ticket(
var contact_email : String? = null,
#JsonSerialize(using = ToStringSerializer::class)
#JsonDeserialize(using = LocalDateTimeDeserializer::class)
var date_opened : LocalDateTime? = null
)
How to fixed it?

Your issue is more about jackson rather than kotlin.
As stated in serialize/deserialize java 8 java.time with Jackson JSON mapper
you need to add an additional gradle dependency to solve it
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.5")
after that it should work
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import org.testng.annotations.Test
import java.time.LocalDateTime
class SoTest {
data class Ticket(
var contact_email: String? = null,
var date_opened: LocalDateTime? = null
)
#Test
fun checkSerialize() {
val mapper = ObjectMapper()
mapper.registerModule(JavaTimeModule())
val ticket = mapper.readValue(inputJsonString, Ticket::class.java)
assert ("$ticket"=="Ticket(contact_email=contact#ema.il, date_opened=2017-11-13T06:40)")
}
val inputJsonString = """{
"contact_email": "contact#ema.il",
"date_opened": "2017-11-13T06:40:00Z"
}""".trimIndent()
}

Related

Kotlin validator for List<Pair<A, B>> doesn't work

I have a data class which I need to validate:
import javax.validation.Valid
import whatever.pckg.validation.PkiSignWithBusinessCode
import whatever.pckg.validation.NullOrNotBlank
data class UploadFileReq(
val id: String? = null,
...(other fields)...
#get:Valid
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>> = mutableListOf() # Object here is for simplicity
) {
#PkiSignWithBusinessCode
data class SignatureInfo(
val typeSign: String = "",
#get:NullOrNotBlank
val businessCode: String? = null,
)
}
#NullOrNotBlank annotation is just a simple merge of standard #NotBlank and #Null annotations.
I also have another custom validation annotation #PkiSignWithBusinessCode, its definition is below:
import whatever.pckg.UploadFileReq
import javax.validation.*
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.reflect.KClass
#Constraint(validatedBy = [PkiSignWithBusinessCodeValidator::class])
#Target(AnnotationTarget.CLASS)
#Retention(RUNTIME)
annotation class PkiSignWithBusinessCode(
val message: String = "PKI signature requires filled businessCode",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class PkiSignWithBusinessCodeValidator: ConstraintValidator<PkiSignWithBusinessCode, UploadFileReq.SignatureInfo>> {
override fun isValid(obj: UploadFileReq.SignatureInfo?, context: ConstraintValidatorContext): Boolean {
if (obj != null) {
if ((obj.typeSign == "PKI") && (obj.businessCode == null)) {
return false
}
}
return true
}
Logic of above annotation is quite simple - when typeSign equals PKI and businessCode is null, then validator should treat that as invalid object.
For your reference here's a simple unit-test that tries to check the work of #PkiSignWithBusinessCode:
import org.junit.jupiter.api.Test
import whatever.pckg.UploadFileReq
import javax.validation.Validation
import kotlin.test.assertEquals
class PkiSignWithBusinessCodeTest {
#Test
fun `validate PkiSignWithBusinessCodeTest`() {
val validator = Validation.buildDefaultValidatorFactory().validator
val signatureInfo = UploadFileReq.SignatureInfo(
typeSign = "PKI",
businessCode = null
)
val uploadFileReq = UploadFileReq(
null,
signaturesInfo = mutableListOf(signatureInfo to Object)
)
val result = validator.validate(uploadFileReq)
assertEquals(1, result.size)
assertEquals("PKI signature requires filled businessCode", result.first().messageTemplate)
}
}
But this test obviously fails on first assertion state: java.lang.AssertionError: Expected <1>, actual <0>. So no constraint violations found by validator.
The problem is that Spring ignores validation rule of above annotation. As an assumption I suppose that somehow Pair class wrap prevents Spring from using my validation annotation. Maybe it's a bug?
Or maybe I overlooked something in my code?
Found a workaround on this - need to make own ValidatingPair with #Valid annotations on first and second members of this new Pair:
import javax.validation.Valid
data class ValidatingPair<out A, out B>(
#get:Valid
public val first: A,
#get:Valid
public val second: B
) : java.io.Serializable {
override fun toString(): String = "($first, $second)"
}
And make:
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>>
to become
val signaturesInfo: MutableList<ValidatingPair<SignatureInfo, Object>>
Then validation starts working for list members.

Deserializing non-null type by passing a default value in Kotlin

I want to deserialize a non-null field in a request model using custom deserializer in Kotlin like this:
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
data class MyRequest(val foo: Foo) {
data class Foo(val bar: String)
companion object {
object Deserializer : StdDeserializer<Foo>(Foo::class.java) { //This is added to Jackson Module successfully somewhere else
override fun deserialize(jsonParser: JsonParser?, context: DeserializationContext?): Foo {
val node: JsonNode = jsonParser!!.codec.readTree(jsonParser)
return if (node.isNull || node.isTextual.not()) Foo("default")
else Foo(node.asText())
}
}
}
}
But when I send a post request with empty json body, I get this:
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class com.me.myapi.model.request.MyRequest] value failed for JSON property foo due to missing (therefore NULL) value for creator parameter foo which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
Since Foo is a non-null type and I did not pass anything in request body for foo, this is being thrown before deserialization. I wonder if there is a way to handle this exception, such as giving a default value and continue with deserialization step.
With version 2.10.0 of jackson-databind you can have:
data class MyDataClass (
#JsonSetter(nulls = Nulls.SKIP)
val defaultParameter:String="some default value",
)
Also, with version 2.8.4 or above of jackson-kotlin-module you can do:
val mapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()) // "inform" Jackson about Kotlin
...
data class MyDataClass(
val defaultParameter:String="some default value",
)
I have achieved this simply by overriding getNullValue() method of the deserializer:
object Deserializer : StdDeserializer<Foo>(Foo::class.java) {
override fun deserialize(jsonParser: JsonParser?, context: DeserializationContext?): Foo {
val node: JsonNode = jsonParser!!.codec.readTree(jsonParser)
return if (node.isNull || node.isTextual.not()) Foo("default")
else Foo(node.asText())
}
override fun getNullValue(): Foo {
return Foo("default value")
}
}

Klaxon parse null enum

I am trying to parse a json response that include an enum using the Klaxon library and kotlin. Below is a test. If I have a nullable enum, my parsing fails. Is there a way to do this properly? Is it an issue with the Klaxon libary?
import com.beust.klaxon.Klaxon
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
enum class MyEnum { FIRST, SECOND, THIRD }
class WithEnum {
var myVal: MyEnum? = null
}
class EnumTest {
#Test
fun `should deserialize null enum`() {
val parsed = Klaxon().parse<WithEnum>("{ \"myVal\":null}")
assertNotNull(parsed)
assertNull(parsed.myVal)
}
#Test
fun `should deserialize proper enum`() {
val parsed = Klaxon().parse<WithEnum>("{ \"myVal\":\"FIRST\"}")
assertNotNull(parsed)
assertEquals(MyEnum.FIRST, parsed.myVal)
}
}
The response from the above unit test is:
kotlin.TypeCastException: null cannot be cast to non-null type kotlin.String
at com.beust.klaxon.EnumConverter.fromJson(EnumConverter.kt:23)
at com.beust.klaxon.EnumConverter.fromJson(EnumConverter.kt:6)
at com.beust.klaxon.JsonObjectConverter.retrieveKeyValues(JsonObjectConverter.kt:189)
at com.beust.klaxon.JsonObjectConverter.initIntoUserClass(JsonObjectConverter.kt:67)
at com.beust.klaxon.JsonObjectConverter.fromJson(JsonObjectConverter.kt:32)
at com.beust.klaxon.DefaultConverter.fromJsonObject(DefaultConverter.kt:210)
at com.beust.klaxon.DefaultConverter.fromJson(DefaultConverter.kt:32)
at com.beust.klaxon.Klaxon.fromJsonObject(Klaxon.kt:296)
at EnumTest.should deserialize null enum(EnumTest.kt:30)
...
There source of the error is https://github.com/cbeust/klaxon/blob/master/klaxon/src/main/kotlin/com/beust/klaxon/EnumConverter.kt#L23, where the null is being cast to a String.
I copied the EnumConverter from the github project and made it nullable, changing line 23 to return a null if the value is a null like so:
val name = jv.inside as String? ?: return null
I then created an annotation for the fields and added the converter to the parser. The EnumConverter() reference below is my copy of it with the Elvis operator addition above.
...
#Target(AnnotationTarget.FIELD)
annotation class NullableEnum
class WithEnum (
#NullableEnum
var myVal: MyEnum? = null
)
class EnumTest {
#Test
fun `should deserialize null enum`() {
val parsed = Klaxon()
.fieldConverter(NullableEnum::class, EnumConverter())
.parse<WithEnum>("{ \"myVal\":null}")
assertNotNull(parsed)
assertNull(parsed.myVal)
}
...

Inserting data into database returns MismatchedInputException error

I am trying to insert some data into the database, and am getting the following error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `org.joda.time.DateTime` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('2019-04-19')
My content negotiation
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
}
}
And my model:
data class User(
//some codes
val registrationDate = DateTime // org.joda.time.DateTime
)
And when will I send by json:
{
//some other data
"registrationDate" : "2019-07-15"
}
Can someone help me, please?
You have to install the Joda module for Jackson https://github.com/FasterXML/jackson-datatype-joda and add it to your jackson configuration in ktor :
install(ContentNegotiation) {
jackson {
registerModule(JodaModule())
enable(SerializationFeature.INDENT_OUTPUT)
}
}
You can also control serialization/deserialization behavior with annotations on your data class properties :
data class Account(
val uid: String? = null,
val firstName: String,
val lastName: String,
val email: String,
#get:JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
val createdTime: DateTime? = null
)

Using data class in micronaut properties

I'm writing configuration properties and would like to use data class to hold the data.
Problem is, data classes have a primary constructor and are immutable, and micronaut tries to inject the values as beans.
Example:
#ConfigurationProperties("gerencianet")
data class GerenciaNetConfiguration(
val clientId: String,
val clientSecret: String,
val apiUrl: String,
val notificationUrl: String,
val datePattern: String = "yyyy-MM-dd HH:mm:ss"
)
Error: Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [java.lang.String] exists. Make sure the bean is not disabled by bean requirements
Is there support for it?
You can inject the values as constructor parameters using #Parameter. It avoids common mistakes with #Value.
For example, if your application.yml looks like this:
group:
first-value: asdf
second-value: ghjk
Then a Kotlin class might look like:
import io.micronaut.context.annotation.Property
import javax.inject.Singleton
#Singleton
class MyClass(#Property(name = "group.first-value") val firstValue: String) {
fun doIt(): String {
return firstValue
}
}
Or, similarly, a method:
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Property
import javax.inject.Singleton
#Factory
class MyFactory {
#Singleton
fun getSomeValue(#Property(name = "group.first-value") firstValue: String): SomeClass {
return SomeClass.newBuilder()
.setTheValue(firstValue)
.build()
}
}
One option you have is to do something like this:
import io.micronaut.context.annotation.ConfigurationBuilder
import io.micronaut.context.annotation.ConfigurationProperties
#ConfigurationProperties("my.engine")
internal class EngineConfig {
#ConfigurationBuilder(prefixes = ["with"])
val builder = EngineImpl.builder()
#ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "crank-shaft") / <3>
val crankShaft = CrankShaft.builder()
#set:ConfigurationBuilder(prefixes = ["with"], configurationPrefix = "spark-plug")
var sparkPlug = SparkPlug.builder()
}
That is from our test suite at https://github.com/micronaut-projects/micronaut-core/blob/1c3e2c3280da200c96e629a4edb9df87875ef2ff/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/config/builder/EngineConfig.kt.
You can also inject the values as constructor parameters using #Value.
I hope that helps.