How to deserialize an array of values into a collection using kotlinx serialization - kotlin

Hi I am a newbie to kotlinx serialization and I am using KMP, my requirement is a little different
my data class
#Serializable data class Student(val name : String , val age : Int)
and my simple JSON would be "['Avinash', 22]",
which should be deserialized to Student("Avinash", 22)
I'm not able to deserialize it can anyone help me

While input such as [Avinash, 22] is not well formed Json, you can still
work with it by parsing it into a JsonElement:
import kotlinx.serialization.json.*
data class Student(val name: String, val age: Int)
fun decode(stringData: String, parser: Json): List<Student> {
val element: JsonArray = parser.parseToJsonElement(stringData).jsonArray
return element.windowed(2, 2).map {
Student(
it[0].toString(),
it[1].toString().toInt()
)
}
}
fun main() {
val parser = Json { isLenient = true }
val students = decode("[A, 22, B, 33, C, 44]", parser)
println(students)
// [Student(name=A, age=22), Student(name=B, age=33), Student(name=C, age=44)]
}

Try this:
val student: Student = Json.decodeFromString("{\"name\": \"Avinash\", \"age\": \"22\"}")
Pay attention how to format your JSON string.
[] square brackets are for arrays
{} curly brackets are for objects
And you must provide your fields name, and use double quotes for fields and values, or use a less strict Json deserialization:
val json = Json {
isLenient = true
}
val student: Student = json.decodeFromString("{name: Avinash, age: 22}")
If you want a deep view on json schema, you can read here.

Related

map a nested list to another nested list in Kotlin

I have a nested list of People : List<List<People.>>, where People has two attribute int age, String name.
I want to map it to a nested list of Student, Student also has two attribute int age, String name.
So the output is List<List<Student.>>.I have looked at examples of mapping a List to another, something like this:
fun List<People>.convert(): List<Student>{
return this.map {
Student(
age = this.age,
name = this.name
)
}
}
How to do it with a nested list? Thanks in advance.
map is one of Kotlin's collection transformation operations. That link explains how it works.
Let's fix your List<People>.convert() function first. Here's one way to write it:
data class Person(val name: String, val age: Int)
data class Student(val name: String, val age: Int)
fun List<Person>.convert(): List<Student> {
return this.map { person ->
Student(
age = person.age,
name = person.name,
)
}
}
Note that inside the mapping function, this does not refer to anything, which is why your original code doesn't compile.
Since the mapping function we're passing to map has only one parameter, we can skip declaring the parameter, and refer to the argument by the special name it instead, like this:
fun List<Person>.convert(): List<Student> {
return this.map { // it is Person
Student(
age = it.age,
name = it.name,
)
}
}
Then, to convert a List<List<Person>> to a List<List<Student>> we could write:
val listsOfPeople: List<List<Person>> = listOf(
listOf(Person("Alice", 27)),
listOf(Person("Bob", 23), Person("Clarissa", 44))
)
val listsOfStudents: List<List<Student>> = listsOfPeople.map { // it is List<Person>
it.convert()
}
Or, if you decide you don't need the convert function, you could write it like this:
val listsOfStudents: List<List<Student>> =
listsOfPeople.map { // it is List<Person>
it.map { // it is Person
Student(it.name, it.age)
}
}

Use Int as a parameter for Kotlin data class

got a bit of a problem with creating a data class. First post, and a bit of a noob - be gentle :)
I'm trying to create a Kotlin data class to handle some responses from an API we use; the response of which looks a bit like this:
"data": {
"devices": {
"600": [
{
"device_id": "[deviceId]", ...
What I'm having trouble with is the "600" bit - I can't find a way to create the data class with this as a parameter. Each time I declare the var/val - it's throwing an error, but doesn't provide any helpful options in the IDE. All the rest are strings, so "devices" becomes "val devices: String" and so on. But in this case the val is an Int, and I don't know how to declare this in the data class.
I want to have the API response re-worked to something more easily defined, but that'll take time. Can anyone tell me how I can pass the Int as the parameter?
This is the data class:
data class SimRetrieveDevicesResponse(
val data: Devices,
val error: String? = null,
)
data class Devices(
val 600: List<DeviceInfo>? = null
)
data class DeviceInfo(
val device_id: String,
val device_type: String,
val network_id: String,
val send_period_sec: Int,
val loss_in_thousand: Int,
val tti_application_id: String,
val cmt_tenant_id: String,
)
Sorry I've called anything the wrong name...
Backtick-ing the val has fixed the error, and helped tremendously.
As stated in the comments, the mapping is more than likely for a map, so having a property called "600", back-ticked or not, is incorrect. As soon as you have another value like "700", you'd have to change your code.
Here's a working solution, based on the assumption from your JSON snippet that devices is a map of a list of device information:
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.kotest.assertions.withClue
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import org.junit.jupiter.api.Test
data class SimRetrieveDevicesResponse(
val data: Devices,
val error: String? = null,
)
data class Devices(val devices: Map<String, List<DeviceInfo>>)
data class DeviceInfo(
val device_id: String,
val device_type: String,
val network_id: String,
val send_period_sec: Int,
val loss_in_thousand: Int,
val tti_application_id: String,
val cmt_tenant_id: String,
)
class StackOverFlowTest {
#Test
fun test() {
val data = """
{
"data": {
"devices": {
"600": [
{
"device_id": "device_id",
"device_type": "device_type",
"network_id": "network_id",
"send_period_sec": 2,
"loss_in_thousand": 3,
"tti_application_id": "tti_application_id",
"cmt_tenant_id": "cmt_tenant_id"
}
]
}
}
}
""".trimIndent()
val mapper = jacksonObjectMapper()
val response = mapper.readValue(data, SimRetrieveDevicesResponse::class.java)
val device = response.data.devices["600"]
withClue("Device should be present") {
device.shouldNotBe(null)
}
device!!.first().device_id shouldBe "device_id"
}
}
The assertions here use kotest, which you can add via this in your build.gradle.kts
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")

What is the easiest way to print a Kotlin data class as compilable code?

I'd like to be able to turn an instance of a fairly simple Kotlin data class into a String that could be copy and pasted into a Kotlin file and would compile.
For example, given these data classes:
data class Parent(val name: String, val age: Int, val children: Set<Child>)
data class Child(val name: String, val age: Int)
I would like a function from any data class to String such that:
toCompilableString(
Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7)))
)
would return
"""Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7)))"""
Does such a thing exist?
We can override the behaviour of toString to output in the desired format:
fun main() {
var amy = Child(name="Amy",age=4)
var bob = Child(name="Bob",age=7)
var joe = Parent(name="Joe", age=34, children=setOf(amy, bob))
print(joe) // outputs "Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7))"
}
data class Parent(val name: String, val age: Int, val children: Set<Child>) {
override fun toString() = "Parent(\"$name\", $age, setOf(${children.joinToString()})"
}
data class Child(val name: String, val age: Int) {
override fun toString() = "Child(\"$name\", $age)"
}
With the help of joinToString(), this will output in the format "Parent("Joe", 34, setOf(Child("Amy", 4), Child("Bob", 7))".
If you really love pain, there are reflection tools made specially for such things. I made a small function that will generate what you need:
fun dataClassToString(instance: Any) {
val sb = StringBuilder()
sb.append("data class ${instance::class.qualifiedName} (")
var prefix = ""
instance::class.memberProperties.forEach{
sb.append(prefix)
prefix = ","
sb.append("${it.name} = ${it.getter.call(instance)}")
}
sb.append(")")
println(sb.toString())
}
The only problem with this function is that for your parent class it generates the following:
data class Parent (age = 34,children = [Child(name=Amy, age=4), Child(name=Bob, age=7)],name = Joe)
Internally set is represented as array, however if you know for sure that you will have only sets or arrays, you can easily check what type it is and append that type when creating the set. You can also check if this is a data class and append it instead of hardcoded string.

How to parse JSON objects into an enum

I have a JSON object that is something like
{
"tsp": "ABC" // can be only one of three things: "ABC", "DEF", "GHI"
"userId" : "lkajsdlk-199191-lkjdflakj"
}
Instead of writing a dataclass along the lines of
data class User(#SerializedName("tsp") val tsp: String, #SerializedName("userId") val userId: String
i'd like to have an enum that defines the three values so that my data class can be
data class User(#SerializedName("tsp") val tsp: TspEnum, #SerializedName("userId") val userId: String
I had tried writing an enum that was
enum class TspEnum(provider: String) {
AY_BEE_CEE("ABC"),
DEE_EE_EFF("DEF"),
GEE_HAYTCH_I("GHI");
}
however that did not work out
I've realized now that calling TspEnum.provider will return the value of the enum, but I'm not sure how to make GSON coƶperate whilst serializing the JSON into the kotlin data class
I've read that there is an issue with Kotlin typing and GSON here: https://discuss.kotlinlang.org/t/json-enum-deserialization-breakes-kotlin-null-safety/11670
however, the way that person is serializing the hair colours to map into an enum is different enough from my tsp json object to make me scratch my head.
Any pointers on where i'm going wrong would be great, cheers!
You can create a deserializer for TspEnum:
class TspDeserializer : JsonDeserializer<TspEnum> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): TspEnum {
val stringValue = json.asString
for (enum in TspEnum.values()) {
if (enum.provider == stringValue) {
return enum
}
}
throw IllegalArgumentException("Unknown tsp $stringValue!")
}
}
next you have to register it:
val gson = GsonBuilder()
.registerTypeAdapter(TspEnum::class.java, TspDeserializer())
.create()
and then you can parse your user:
val user = gson.fromJson(json, User::class.java)
println(user) // prints User(tsp=AY_BEE_CEE, userId=lkajsdlk-199191-lkjdflakj)

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
)