Deserialize object from string - kotlin

I'm trying to deserialize object from string, but get "no String-argument constructor/factory method to deserialize from String value ('test')" exception.
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
class KotlinJacksonTest : FunSpec({
val mapper = jacksonObjectMapper()
test("deserialize as string") {
class Ref #JsonCreator constructor(#JsonValue val name: String)
class Root(val ref: Ref)
val root = mapper.readValue<Root>(""" { "ref": "test"} """)
root.ref.name shouldBe "test"
}
})
What I need is to make jackson serialize my object as if it was a string.
But it constantly fails with the following error:
Cannot construct instance of `KotlinJacksonTest$1$1$Ref` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('test')
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `KotlinJacksonTest$1$1$Ref` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('test')
at [Source: (String)" { "ref": "test"} "; line: 1, column: 11] (through reference chain: KotlinJacksonTest$1$1$Root["ref"])
What I'm doing wrong? I can clearly remember this worked for me in java when I needed to do same but with map instead of string.

Ok, after reading some documentation on JsonCreator annoation turns out that it must be like this:
class Ref #JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(#JsonValue val name: String)
The JsonCreator.Mode.DELEGATING makes it work like I wanted.
UPD: There is even a possibility to make it shorter - custom jackson annotation.
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.CONSTRUCTOR)
#JacksonAnnotationsInside
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
annotation class JsonDelegating
And usage:
class Ref #JsonDelegating constructor(#JsonValue val name: String)

You can avoid having to annotate the constructor of your data class completely by just targeting the #JsonValue annotation on your property to its getter method, like so:
class Ref(#get:JsonValue val name: String)
Notably #field:JsonValue does not work in this case either.
Why your original code, nor the field targeting, do not work as expected, I couldn't tell you.

Related

Why #JsonIgnore annotation doesn't work during deserializing data?

I have a data https://gist.githubusercontent.com/iva-nova-e-katerina/fc1067e971c71a73a0b525a21b336694/raw/954477261bb5ac2f52cee07a8bc45a2a27de1a8c/data2.json a List with seven CheckResultItem elements.
I trying to parse them this way:
import com.fasterxml.jackson.module.kotlin.readValue
...
val res = restHelper.objectMapper.readValue<List<CheckResultItem>>(text)
which gives me the following error:
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.fmetric.validation.api.Brick] value failed for JSON property upperLevelBricks due to missing (therefore NULL) value for creator parameter upperLevelBricks which is a non-nullable type
at [Source: (StringReader); line: 1, column: 714] (through reference chain: java.util.ArrayList[0]->com.fmetric.validation.api.checking.CheckResultItem["brick"]->com.fmetric.validation.api.Brick["upperLevelBricks"])
at com.fasterxml.jackson.module.kotlin.KotlinValueInstantiator.createFromObjectWith(KotlinValueInstantiator.kt:116)
There is #JsonIgnore annotation in data class :
data class Brick(
val id: UUID?,
val name: String,
val type: BrickType,
val propertyValues: List<ProjectBrickPropertyValue<*>>,
#JsonIgnore
val upperLevelBricks: ArrayList<Brick>,
val downLevelBricks: ArrayList<Brick>,
var drawingDetails: List<BrickDrawingDetails>?
) {
But it seems it doesn't work. Could you explain me what is wrong?
UPD: Also I have tried #JsonIgnoreProperties({"upperLevelBricks"}) class annotation but it doesn't work. My solution was to set a default value
val upperLevelBricks: ArrayList<Brick> = arrayListOf(),
But I think that annotations should work!
Actually, it works, but not the way you think. During deserialization #JsonIgnore ignores the respectful field in JSON, like it wasn't there (but it's doesn't make sense in this case, because it's initially absent in JSON).
In Java, Jackson would've just instantiated class with null value for the absent field (because all object types in Java are nullable, which means they allow the value to be set to null). But in Kotlin, a property should be explicitly marked as nullable (val upperLevelBricks: List<Brick>?) or have a default value (val upperLevelBricks: List<Brick> = emptyList()) so that Jackson could create a class instance in this case.
Note that approach with default value for property won't work (unless you additionally mark it with #JsonIgnore) if this field is present in JSON but explicitly set to null:
{
...
"upperLevelBricks": null,
...
}
Anyway, if you don't want to change the API of your Brick class you may provide a default value for this field only when it's created during Jackson deserialization (and only if it's absent/null in JSON) via custom deserializer:
object EmptyListAsDefault : JsonDeserializer<List<Brick>>() {
override fun deserialize(jsonParser: JsonParser, context: DeserializationContext): List<Brick> =
jsonParser.codec.readValue(
jsonParser,
context.typeFactory.constructCollectionType(List::class.java, Brick::class.java)
)
override fun getNullValue(context: DeserializationContext): List<Brick> = emptyList()
}
data class Brick(
//...
#JsonDeserialize(using = EmptyListAsDefault::class)
val upperLevelBricks: List<Brick>,
//...
)

GSON Deserialization of subtypes in Kotlin

I'm not sure if this is a limitation, a bug or just bad use of GSON. I need to have a hierarchy of Kotlin objects (parent with various subtypes) and I need to deserialize them with GSON. The deserialized object has correct subtype but its field enumField is actually null.
First I thought this is because the field is passed to the "super" constructor but then I found out that "super" works well for string, just enum is broken.
See this example:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory
open class Parent(val stringField: String,
val enumField: EnumField) {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2)
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type)
val subtypeRAF = RuntimeTypeAdapterFactory.of(Parent::class.java, "enumField")
.registerSubtype(Subtype1::class.java, Parent.EnumField.SUBTYPE1.name)
.registerSubtype(Subtype2::class.java, Parent.EnumField.SUBTYPE2.name)
.registerSubtype(Subtype3::class.java, Parent.EnumField.SUBTYPE3.name)
fun main() {
val gson = GsonBuilder()
.registerTypeAdapterFactory(subtypeRAF)
.create()
serializeAndDeserialize(gson, Subtype1()) // this works (but not suitable)
serializeAndDeserialize(gson, Subtype2("s2")) // broken
serializeAndDeserialize(gson, Subtype3("s3", Parent.EnumField.SUBTYPE3)) // broken
}
private fun serializeAndDeserialize(gson: Gson, obj: Parent) {
println("-----------------------------------------")
val json = gson.toJson(obj)
println(json)
val obj = gson.fromJson(json, Parent::class.java)
println("stringField=${obj.stringField}, enumField=${obj.enumField}")
}
Any ideas how to achieve to deserialization of enumField?
(deps: com.google.code.gson:gson:2.8.5, org.danilopianini:gson-extras:0.2.1)
P.S.: Note that I have to use RuntimeAdapterFactory because I have subtypes with different set of fields (I did not do it in the example so it is easier to understand).
Gson requires constructors without arguments to work properly (see deep-dive into Gson code below). Gson constructs raw objects and then use reflection to populate fields with values.
So if you just add some argument-less dummy constructors to your classes that miss them, like this:
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2) {
constructor() : this("")
}
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type) {
constructor() : this("", EnumField.SUBTYPE3)
}
you will get the expected output:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
Gson deep-dive
If you want to investigate the internals of Gson, a tip is to add an init { } block to Subtype1 since it works and then set a breakpoint there. After it is hit you can move up the call stack, step through code, set more breakpoints etc, to reveal the details of how Gson constructs objects.
By using this method, you can find the Gson internal class com.google.gson.internal.ConstructorConstructor and its method newDefaultConstructor(Class<? super T>) that has code like this (I have simplified for brevity):
final Constructor<? super T> constructor = rawType.getDeclaredConstructor(); // rawType is e.g. 'class Subtype3'
Object[] args = null;
return (T) constructor.newInstance(args);
i.e. it tries to construct an object via a constructor without arguments. In your case for Subtype2 and Subtype3, the code will result in a caught exception:
} catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
return null; // set breakpoint here to see
}
i.e. your original code fails since Gson can't find constructors without arguments for Subtype2 and Subtype3.
In simple cases, the problem with missing argument-less constructors is worked around with the newUnsafeAllocator(Type, final Class<? super T>)-method in ConstructorConstructor, but with RuntimeTypeAdapterFactory that does not work correctly.
I may be missing something in what you're trying to achieve, but is it necessary to use the RuntimeTypeAdapterFactory? If we take out the line where we register that in the Gson builder, so that it reads
val gson = GsonBuilder()
.create()
Then the output returns the enum we would expect, which looks to be serialising / deserialising correctly. I.e. the output is:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
It also may be an idea to implement Serializable in Parent. i.e.
open class Parent(val stringField: String, val enumField: EnumField) : Serializable {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
Try adding #SerializedName annotation to each enum.
enum class EnumField {
#SerializedName("subtype1")
SUBTYPE1,
#SerializedName("subtype2")
SUBTYPE2,
#SerializedName("subtype3")
SUBTYPE3
}

what is the meaning of the first line in the below kotlin code. Newbee to Kotlin from Java

I am a Java programmer and new to Kotlin. Please help me understand the below code, especially the first line.
class SiteListEventBus : EventBus<SiteListEventBus.SiteListChangeEvent, String, NotificationHandler<SiteListEventBus.SiteListChangeEvent>>() {
data class SiteListChangeEvent(val entityId: String, val routingKey: String)
override fun getSubscriptionKey(event: SiteListChangeEvent?): String {
return event!!.routingKey
}
}
class SiteListEventBus :EventBus<SiteListEventBus.SiteListChangeEvent, String,
NotificationHandler<SiteListEventBus.SiteListChangeEvent>>() {
So from what im gathering here EventBus would be like your base class which SiteListEventBus is inheriting from and EventBus which conforms to or includes 3 type parameters
Which are SiteListEventBus.SiteListChangeEvent as type 1,
String as type 2,
then NotificationHandler as type 3 which then has a type parameter of SiteListEventBus.SiteListChangeEvent little complicated there
data class SiteListChangeEvent(val entityId: String, val routingKey: String)
This data class then would just be the parameters/variables SiteListChangeEvent which would be your entityId of type string and your routingKey of type string
override fun getSubscriptionKey(event: SiteListChangeEvent?): String {
return event!!.routingKey
}
this last method overrides your getter for subscription key passes in your event which is SiteListChangeEvent? which is an optional value from the ? (so this can be null) to be used and its expecting a String for a return type
then your returning your passed in event!!.routingKey. the not-null assertion operator (!!) converts any value to a non-null type and throws an exception if the value is null.
So, you can write event!!, and this will return a non-null value of event (e.g., a String in your example) or throw a null pointer exception if event is null: soooo this seems like a bad idea because if event is null this will crash for sure
if you need further explanation let me know and ill go into further detail
Here is how I read the first line:
class SiteListEventBus
Define a new class.
: FooBar()
Extend the class FooBar using the empty constructor.
FooBar is actually EventBus<SiteListEventBus.SiteListChangeEvent, String, NotificationHandler<SiteListEventBus.SiteListChangeEvent>>
Generics apply here the way you would expect in Java.
class SiteListEventBus : FooBar() {
Begin implementing the SiteListEventBus class.
Here is how I read the rest:
data class SiteListChangeEvent(val entityId: String, val routingKey: String)
Create a data class.
override fun getSubscriptionKey
The override is similar to the #Override annotation. Override the method getSubscriptionKey.
event!!.routingKey
The event variable is nullable. I recommend reading about the !! operator.

DynamoDBMapper load cannot instantiate Kotlin data class

Using DynamoDBMapper within an AWS Lambda (i.e. not Android) written in Kotlin, I can save a record using a data class. However when I attempt to load a record to a data class, I receive a "DynamoDBMappingException: could not instantiate class" exception.
#DynamoDBTable(tableName = "Test")
data class TestItem(
#DynamoDBHashKey(attributeName="someKey")
#DynamoDBAttribute(attributeName = "someKey")
var someKey: String?,
#DynamoDBAttribute(attributeName = "someValue")
var someValue: String?
}
val ddbMapper = DynamoDBMapper(AmazonDynamoDBClientBuilder.defaultClient())
ddbMapper.load(TestItem::class.java, "xyz")
Results in the following exception:
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException:
could not instantiate class
com.intuit.connect_to_pro.lambda_common_core.aws_service.TestItem
With the root exception being:
java.lang.NoSuchMethodException:
com.intuit.connect_to_pro.lambda_common_core.aws_service.TestItem.()
AWS has an example for Android that uses com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapper instead of com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper. I tried the Android version, but the result was the same.
https://docs.aws.amazon.com/aws-mobile/latest/developerguide/add-aws-mobile-nosql-database.html
Any help would be appreciated.
The DynamoDBMapper expects a class with an empty constructor. Using a Kotlin data class, you can specify default values for all parameters and use #JvmOverload, which will generate the empty constructor for JVM (Java). Also all parameters need to be mutable, so you need to use "var" instead of "val".
#DynamoDBTable(tableName = "Test")
data class TestItem #JvmOverloads constructor(
#DynamoDBHashKey(attributeName="someKey")
var someKey: String = "",
var someValue: String = ""
)
Make sure that all your classes have an empty constructor. In my case I had nested documents. Those had to have empty constructors too.
In Kotlin, an empty (parameterless) constructor will be created if you specify default values for all the attributes.
Also, make sure that the data from the db can be converted to the data in your classes.
For example, mine failed because I had an Integer property in my class while in the db I had a String. i.e. I had the String value "30" in the db, instead of the Integer value 30.

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