Ktor with Kmongo and kotlinx.serialization - kotlin

I have a setup where I use KTor with KMongo and Kotlinx.Serialization.
The Kmongo part works, I can get and put my Class
#Serializable
data class Task(#ContextualSerialization #SerialName("_id") val _id : Id<Task> = newId(),
val title : String = "",
val description : String = ""
)
Into the database and retrieve it. That all works flawlessly.
But when I try to send that object through a rest call to the frontend, again with Kotlinx.Serialization.
get<Tasks>{ task ->
val dao by di().instance<Dao>();
val task = Task( title = "task1", description = "task1description");
val foundTask = dao.read(task);
if(foundTask != null){
call.respond(foundTask)
} else {
call.respond("didn't find anything")
}
}
It throws this expection:
kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class WrappedObjectId. For generic classes, such as lists, please provide serializer explicitly.
at kotlinx.serialization.PlatformUtilsKt.serializer(PlatformUtils.kt:21)
at kotlinx.serialization.modules.SerialModuleExtensionsKt.getContextualOrDefault(SerialModuleExtensions.kt:29)
at kotlinx.serialization.ContextSerializer.serialize(ContextSerializer.kt:29)
at kotlinx.serialization.json.internal.StreamingJsonOutput.encodeSerializableValue(StreamingJsonOutput.kt:227)
at kotlinx.serialization.builtins.AbstractEncoder.encodeSerializableElement(AbstractEncoder.kt:72)
Now I figured out that this is because there are 2 instances of the kotlin.serialization json. and the one on KMongo does not share it's serializers with the other one.
so I added the serializers from KMongo to the other instance from Ktor
install(ContentNegotiation) {
json(
json = Json(DefaultJsonConfiguration.copy(prettyPrint = true), context = kmongoSerializationModule),
contentType = ContentType.Application.Json
)
}
and now I get
java.lang.ClassCastException: class kotlinx.serialization.json.internal.StreamingJsonOutput cannot be cast to class com.github.jershell.kbson.BsonEncoder (kotlinx.serialization.json.internal.StreamingJsonOutput and com.github.jershell.kbson.BsonEncoder are in unnamed module of loader 'app')
So my question is why is it happening and how to fix it?

Edit: This issue has been fixed.
I also posted this question on the KMongo github and basically got an instant response that this was fixed for Jackson but not yet for Kotlinx.serialization.
Someone is going to fix this.
Edit: It has been fixed

Related

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>

How to use JSONObject from Kotlin JS

I am new to Kotlin JS.
I am trying to port the business logic of my android app to Kotlin JS.
My app uses the class org.json.JsonObject to do custom serialization. I can't use KotlinX serialization with annotations because my classes are inline and these annotations are not supported.
The Kotlin-JS project uses Gradle Kotlin DSL. I am specifying the dependency as "implementation ("org.json:json:20190722")". The compiler throws the error "unresolved reference" for anything from the library. I suspect it is not legal to link to a java library this way for Kotlin-JS. Is this true?
What is the best way to get an implementation of JsonObject into my app? Do I need to copy the source code into my project and compile it to JS myself?
Thanks for any help.
If you don't want to use the annotations in the kotlinx.serizalization library, you can still include it in your android and js platforms.
You will just need to construct and use the JsonObject type that is present on both platforms manually.
You can see the JsonObject definition in the library here:
https://github.com/Kotlin/kotlinx.serialization/blob/ffe216f0293231b09eea24a10aa4bc26ff6d5b90/runtime/commonMain/src/kotlinx/serialization/json/JsonElement.kt#L217
Here is an example of manually constructing a JsonObject using kotlinx.serialization classes
data class AnalyticsEvent(
val name: Event,
val columns: Map<Column, JsonPrimitive>? = null,
val properties: Map<Property, JsonElement>? = null
) : LoggingEvent() {
override fun toJson(): JsonObject {
val content: MutableMap<String, JsonElement> = mutableMapOf()
content[EVENT_NAME_KEY] = JsonPrimitive(name.actual)
val columnJSON = columns?.mapKeys { it.key.actual }
columnJSON?.let {
content[EVENT_COLUMNS_KEY] = JsonObject(columnJSON)
}
val propertiesJSON = properties?.mapKeys { it.key.actual }
propertiesJSON?.let {
content[EVENT_PROPERTIES_KEY] = JsonObject(propertiesJSON)
}
return JsonObject(content)
}

Jackson Deserialization with no Type

I have a question related to Jackson and polymorphism: is there a way to deserialize a JSON string without specifying a type?
Assuming I don't own this message (e.g., external API) and I have two separate messages that come in at separate times:
{
"responseCode": 200
"responseMessage": "You did something successfully"
}
{
"errorCode": 401
"errorDescription": "Permission denied"
}
And I want to deserialize this message with some data classes that I created based on these messages through polymorphism (see abstract class in next code block):
data class MyDataClass(
val responseCode: Int,
val responseMessage: String
): MyAbstractClass()
data class MyOtherDataClass(
val errorCode: Int,
val errorDescription: String
): MyAbstractClass()
And I am resolving these messages through a function that will use the Jackson Object Mapper to deserialize the stringified JSON payload:
#JsonSubTypes(
JsonSubTypes.Type(value = MyDataClass::class),
JsonSubTypes.Type(value = MyOtherDataClass::class)
)
#JsonIgnoreProperties(ignoreProperties = true)
abstract class MyAbstractClass
fun receiveMessage(message: String) {
val convertedMessage = jacksonObjectMapper().readValue<MyAbstractClass>(message)
log.info(convertedMessage)
/* prints either:
MyDataClass(responseCode=200, responseMessage=You did something successfully)
OR
MyOtherDataClass(errorCode=401, errorDescription=Permission denied)
*/
}
But since I haven't described how to identify the data class (using #JsonTypeInfo), it fails.
To repeat, I am curious if there is a way that I can deserialize the incoming message to one of my polymorphic types without having to specify the #JsonTypeInfo. Or if I must describe the #JsonTypeInfo, how would I do this with no similarities between the two child classes of MyAbstractClass?
I would write a custom deserializer which takes it as a generic JSONObject or the like. Then I'd check if a differentiating key exists. For example:
// pseudocode
when (json: JSONObject) {
hasKey("responseCode") -> // deserialize as MyDataClass
hasKey("errorCode") -> // deserialize as MyOtherDataClass
}

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
}

is it possible to add a template to the getter/setter of a data class?

for example , I want to change all setters this way:
this.a = StringUtils.trim(a);
If it's a java bean, I can do this by modifying the code generating template of the ide. But Intellij seems not support to atomically add getter/setter for kotlin data class.
Is there a way to do this?
There is not a way to do this as of Kotlin 1.1.
A Kotlin data class, for the most part, is a class "to do nothing but hold data".
I think the closest you can get is to validate your data upon class initialization and make your data class properties read-only values. e.g.:
data class Data(val a: String) {
init {
require(a == a.trim())
}
}
The following won't throw an exception:
val a = Data("ab")
val b = a.copy(a = "abc")
While the following will:
val c = a.copy(a = "abc ")
It looks like if you declare the property as private, you can create your own getter/setters for accessing it. This example works for me.
fun main(args: Array<String>) {
var t = test("foo")
t.setHello("bar")
println(t)
}
data class test(private var hello: String) {
fun setHello(blah: String) {
this.hello = blah
}
}
But you will still have an issue when the property is passed in to the constructor. You will probably need to rethink how you are doing this, either declaring the field private and trimming it in the getter, or not using a data class for this instance.