Klaxon parse null enum - kotlin

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

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

kotlin-test: How to test for a specific type like: "is y instance of X"

How to test if a val/var is of an expected type?
Is there something I am missing in Kotlin Test, like:
value shouldBe instanceOf<ExpectedType>()
Here is how I implemented it:
inline fun <reified T> instanceOf(): Matcher<Any> {
return object : Matcher<Any> {
override fun test(value: Any) =
Result(value is T, "Expected an instance of type: ${T::class} \n Got: ${value::class}", "")
}
}
In KotlinTest, a lot is about proper spacing :)
You can use should to get access to a variety of built-in matchers.
import io.kotlintest.matchers.beInstanceOf
import io.kotlintest.should
value should beInstanceOf<Type>()
There is also an alternative syntax:
value.shouldBeInstanceOf<Type>()
See here for more information.
Since Kotlin 1.5 there is a nice solution included in kotlin-test:
assertIs<TypeExpected>(value)
This will not only assert that value is of type TypeExpected, it will also smart cast value, so that you can access all methods of TypeExpected. Just include the dependency, e.g:
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.7.10")
And you can do stuff like this
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
class AssertIsTests {
#Test
fun `test for list`() {
val list = Collector.List.collect(1, 1, 2, 2, 3)
assertEquals(5, list.size)
// assertEquals(1, list[0]) won't compile: list is of type Collection<Int> which doesn't support []
assertIs<List<Int>>(list) // from now on list is of type List<Int>!
assertEquals(4, list.indexOf(3)) // now there are all list methods
assertEquals(1, list[0]) // including indexed getters
}
#Test
fun `test for set`() {
val set = Collector.Set.collect(1, 1, 2, 2, 3)
assertEquals(3, set.size) // Set with duplicates removed
assertIs<Set<Int>>(set) // from now on set is of Type Set<Int>
}
}
enum class Collector {
List {
override fun collect(vararg args: Int) = args.toList()
},
Set {
override fun collect(vararg args: Int) = args.toSet()
};
abstract fun collect(vararg args: Int): Collection<Int>
}

Kotlin data class and LocalDateTime

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

Convert static variables from Java to Kotlin

I'm trying to convert the following code to Kotlin AND still have one of the classes (Foo) used by Java. What is the proper way of making this conversion?
Original Java:
public class Foo {
public static final String C_ID = "ID";
public static final String C_NAME = "NAME";
public static final String[] VALUES = {"X", "Y", "Z"};
public static String[] getAll() {
return new String[] {C_ID, C_NAME};
}
}
public class Bar {
public void doStuff() {
String var1 = Foo.C_ID;
String[] array1 = Foo.VALUES;
String[] array2 = Foo.getAll();
}
}
Auto conversion fo Foo to Kotlin
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
Problem:
Bar class can no longer access C_ID or VALUES (error: "private access")
if I put "const" in front of C_ID, it works... but I cannot do the same with VALUES ("const" can ONLY be used on primatives or String)
Is there a different way I should be doing this (so both Java code and Kotlin code can access everything in Foo)?
The current semantics come from Kotlin Beta Candidate:
#JvmField and objects
We have made the strategy for generating pure fields (as opposed to get/set pairs) more predictable: from now on only properties annotated as #JvmField, lateinit or const are exposed as fields to Java clients. Older versions used heuristics and created static fields in objects unconditionally, which is against our initial design goal of having binary-compatibility-friendly APIs by default.
Also, singleton instances are now accessible by the name INSTANCE (instead of INSTANCE$).
According to this and to the reference, there are three ways of working with properties of a Kotlin object from Java:
Use Foo.INSTANCE.
By default, properties of object won't be static fields for Java, but Java can access the properties through Foo object instance -- Foo.INSTANCE.
So the expression will be Foo.INSTANCE.getC_ID().
Mark a property with #JvmStatic annotation:
object Foo {
#JvmStatic val C_ID = "ID"
//...
}
This will generate static getter for C_ID instead of Foo instance getter which will be accessible as Foo.getC_ID().
Use #JvmField annotation on property declaration:
object Foo {
#JvmField val C_ID = "ID"
//...
}
This will make Kotlin compiler generate a static field for Java instead of property.
Then in Java you can access it as a static field: Foo.C_ID.
But it won't work on properties without backing fields like all in your example.
For primitives, as you stated, one can use const which will have the same effect as #JvmField in terms of visibility in Java.
By the way, when it comes to methods, the situation is the same, and there is #JvmStatic annotation for them.
In your foo class you can put those properties and the method inside a companion object:
class Foo {
companion object {
val C_ID:String = "ID"
val C_NAME:String = "NAME"
#JvmField val VALUES = arrayOf("X", "Y", "Z")
fun getAll():Array<String> {
return arrayOf(C_ID, C_NAME)
}
}
}
Then you can call Foo.getAll(), and Foo.C_ID, Foo.C_NAME and Foo.VALUES.
You should be able to access the values "the kotlin way":
object Foo {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
fun main(args: Array<String>) {
Foo.all.forEach { it->println(it) }
}
With as result:
ID
NAME
Process finished with exit code 0
it's better if you create new kotlin file just for constants.
create Constants.kt file and paste below code.
object Constants {
val C_ID = "ID"
val C_NAME = "NAME"
val VALUES = arrayOf("X", "Y", "Z")
val all: Array<String>
get() = arrayOf(C_ID, C_NAME)
}
in your main activity you can access the constants by the constant name the android studio will automatically import the constants. here is my mainActivity:
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.architecturecompintro.Constants.C_ID
import com.example.architecturecompintro.Constants.C_NAME
import com.example.architecturecompintro.Constants.VALUES
import com.example.architecturecompintro.Constants.all
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val TAG = "info"
Log.i(TAG, C_ID)
Log.i(TAG,C_NAME)
for(item in VALUES) {
Log.i(TAG,item)
}
val arrayItem = all
for(item in arrayItem) {
Log.i(TAG,item)
}
}
}
I was able to get log output successfully