How to convert a JSON string to an Object in KMM - json

Previously, I asked this question: Implementing generic method in interface that uses implementors class which allowed for an object to be converted to a JSON string.
But, now I would like to reverse the process. Ideally this would look like:
interface Domain {
constructor(json: String) {
/*...*/
}
}
#Serializable
class User(val a: Int, val b: Int): Domain {}
val user = User("{a: 3, b: 4}")
But, I'm unable to figure out how to construct an object directly from a JSON string.
A next best option would be create a static generator method:
interface Domain {
companion object {
inline fun <reified T> fromJSON(json: String): T {
return Json.decodeFromString(json)
}
}
}
val user = User.fromJSON("{a: 3, b: 4}")
But, this doesn't work at all because User does not inherit Domain's companion object. The 3rd best option:
val user = Domain.fromJSON<User>("{a: 3, b: 4}")
This does work from the Android side, however since fromJSON is declared inline and reified it is not exposed to iOS at all from the KMM.
Which brings me to my current solution:
#Serializable
class User(val a: Int, val b: Int): Domain {
companion object {
fun fromJSON(json: String): User { return Json.decodeFromString(json) }
}
}
val user = User.fromJSON("{a: 3, b: 4}")
This works, however it requires the above boilerplate code to be added to each and every 'Domain' object.
Is there anyway to improve on my current solution? (Of course, the higher up the chain the better.)

I think you have Object then you should need to convert it as :--
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.codewithfun.kotlin.jsonparser.models.Tutorial
fun main(args: Array<String>) {
val gson = Gson()
val gsonPretty = GsonBuilder().setPrettyPrinting().create()
val tutsList: List<Tutorial> = listOf(
Tutorial("Tut #1", "bezkoder", listOf("cat1", "cat2")),
Tutorial("Tut #2", "zkoder", listOf("cat3", "cat4"))
);
val jsonTutsList: String = gson.toJson(tutsList)
println(jsonTutsList)
val jsonTutsListPretty: String = gsonPretty.toJson(tutsList)
println(jsonTutsListPretty)
}
Then the output be like :--
[{"title":"Tut #1","author":"bezkoder","categories":["cat1","cat2"]},{"title":"Tut #2","author":"zkoder","categories":["cat3","cat4"]}]
[
{
"title": "Tut #1",
"author": "bezkoder",
"categories": [
"cat1",
"cat2"
]
},
{
"title": "Tut #2",
"author": "zkoder",
"categories": [
"cat3",
"cat4"
]
}
]

Related

Kotlinx Deserialization Object or List of Object to List of Object

I am currently implementing an API client with Ktor. The API I am requesting does not return a consistent JSON format.
for Example:
sometimes the JSON looks like this:
{
"description": {
"lang": "en",
"value": "an English description..."
},
...
}
and sometimes like this:
{
"description": [
{
"lang": "en",
"value": "an English description..."
},
{
"lang": "fr",
"value": "a French description..."
}
],
...
}
Now my Question:
How can I implement a Custom Kotlinx Deserializer to Decode an Object of T or a List<T> to a List<T>
My classes look like this:
#Serializable
class ResourceResponse(
#SerialName("description")
val descriptions: List<Description>
) {
#Serializable
data class Description(
#SerialName("value")
val value: String,
#SerialName("lang")
val language: String,
)
}
I want that a Json with only one Description-Object will be deserialized to a List with one Object and not specifically for the description, but in general for classes.
I've found nothing really helpful in the Web.
One solution is to first deserialize it to JsonElement, introspect and then decide how to deserialize it further into ResourceResponse:
fun decode(s: String): ResourceResponse {
val json = Json.parseToJsonElement(s).jsonObject
return when (val desc = json["description"]) {
is JsonArray -> Json.decodeFromJsonElement(json)
is JsonObject -> {
val json2 = json.toMutableMap()
json2["description"] = JsonArray(listOf(desc))
Json.decodeFromJsonElement(JsonObject(json2))
}
else -> throw IllegalArgumentException("Invalid value for \"description\": $desc")
}
}
This solution is definitely not ideal. It may be potentially less performant as we need to deserialize the whole tree into the tree of JsonElement objects only to transform it to the final types (although, maybe the library does this internally anyway). It works only for json and it is tricky to use this solution if ResourceResponse is somewhere deep into the data structure.
You can use a JsonContentPolymorphicSerializer to choose a deserializer based on the form of the JSON.
This one should work:
#Suppress("UNCHECKED_CAST")
class DescriptionsSerializer : JsonContentPolymorphicSerializer<List<ResourceResponse.Description>>(
List::class as KClass<List<ResourceResponse.Description>>
) {
// Here we check the form of the JSON we are decoding, and choose
// the serializer accordingly
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out List<ResourceResponse.Description>> {
return if (element is JsonArray)
ListSerializer(ResourceResponse.Description.serializer())
else
SingleDescriptionAsList()
}
class SingleDescriptionAsList : KSerializer<List<ResourceResponse.Description>> {
override val descriptor: SerialDescriptor
get() = ResourceResponse.Description.serializer().descriptor
override fun deserialize(decoder: Decoder): List<ResourceResponse.Description> {
return listOf(ResourceResponse.Description.serializer().deserialize(decoder))
}
override fun serialize(encoder: Encoder, value: List<ResourceResponse.Description>) {
throw Exception("Not in use")
}
}
}
You must also amend your original class to tell it to use this serializer:
#Serializable
class ResourceResponse(
#SerialName("description")
#Serializable(with = DescriptionsSerializer::class) val descriptions: List<Description>
) {
#Serializable
data class Description(
#SerialName("value")
val value: String,
#SerialName("lang")
val language: String,
)
}
Then you will be able to decode JSON objects with the single key "descriptions" using the ResourceResponse serializer.
For avoidance of doubt, if there are other keys in the JSON (it's not entirely clear from the question) then those should also be written into ResourceResponse definition.
After my research, I have now come up with a solution. For this you need a wrapper class. (here GenericResponse). I hope I can help others who have the same problem.
This is the Wrapper-Class
#Serializable(with = ListOrObjectSerializer::class)
class GenericResponse<T>(
val data: List<T> = emptyList()
) {
private var _isNothing : Boolean = false
val isNothing: Boolean
get() {
return this._isNothing
}
companion object {
fun <T> nothing(): GenericResponse<T> {
val o = GenericResponse(emptyList<T>())
o._isNothing = true
return o
}
}
}
And the Serializer looks like:
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
class ListOrObjectSerializer<T : Any>(private val tSerializer: KSerializer<T>): KSerializer<GenericResponse<T>> {
override val descriptor: SerialDescriptor
get() = tSerializer.descriptor
override fun deserialize(decoder: Decoder): GenericResponse<T> {
val input = decoder as JsonDecoder
val jsonObj = input.decodeJsonElement()
return when(jsonObj) {
is JsonObject -> GenericResponse(listOf(Json.decodeFromJsonElement(tSerializer, jsonObj)))
is JsonArray -> GenericResponse(Json.decodeFromJsonElement(ListSerializer(tSerializer), jsonObj))
else -> return GenericResponse.nothing()
}
}
override fun serialize(encoder: Encoder, value: GenericResponse<T>) {
throw IllegalAccessError("serialize not supported")
}
}
My Data-Class look now like:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
class ResourceResponse(
#SerialName("description")
val descriptions: GenericResponse<Description>? = null,
) {
#Serializable
data class Description(
#SerialName("value")
val value: String? = null,
#SerialName("lang")
val language: String? = null,
)
}
data class ResourceResponse(
#SerializedName("description") val descriptions: List<Description>,
)
data class Description(
#SerializedName("value") val value: String,
#SerializedName("lang") val language: String,
)
it should be like that

Deserialize JSON with "non-stable" values using kotlin serialization

I have not very well structured JSON from BE (that I cannot change) which was formerly handled with moshi (custom adapter for the issue). Now I am trying to use pure kotlin serialization instead, but as said, JSON structure doesn't help me much.
{
"foo": {
"version":1,
"mask": [
{
"values": [
{ "bar": 1, ... }
]
},
{
"values": [
"important text i guess"
]
},
]
}
}
Now as you can see my issue is with values that can contains both object as well as string. All this should be parsed into kotlin where values looks like this:
data class Values(
val bar: Int? = null,
val text: String? = null,
...
)
Theoretically I can change local implementation, e.g. split the values class or something.
I've already tried to apply Polymorphic deserialization, but as I understand it was not able to recognise difference between two descendants of values without classDiscriminator set in JSON.
Any good advice?
Update
The polymorphic version I tried (in case I just made some error)
#Polymorphic
#Serializable
sealed class Values{
#Serializable
data class ObjectValues(
val bar: Int? = null,
...
)
#Serializable
data class TextValues(
val text: String? = null
)
}
and use it:
Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
polymorphic(Values::class) {
subclass(Values.ObjectValues::class, Values.ObjectValues.serializer())
subclass(Values.TextValues::class, Values.TextValues.serializer())
}
}
error:
Polymorphic serializer was not found for missing class discriminator ('null')
JSON input: {"bar":42,...}
Disclaimer
I know that this would fix it all :)
"values": [ { "text":"important text i guess" } ]
Having classDiscriminator is not a mandatory requirement for polymorphic deserialization. You can use content-based polymorphic deserialization in this case.
All you need is to define a JsonContentPolymorphicSerializer for Values class with some logic for exact subclass serializer selection:
object ValuesSerializer : JsonContentPolymorphicSerializer<Values>(Values::class) {
override fun selectDeserializer(element: JsonElement) = when {
element.jsonObject["values"]!!.jsonArray.first() is JsonObject -> ObjectValues.serializer()
else -> TextValues.serializer()
}
}
and wire it as a serializer for Values class:
#Serializable(with = ValuesSerializer::class)
sealed class Values
#Serializable
data class TextValues(val values: List<String>) : Values()
#Serializable
data class ObjectValues(val values: List<Bar>) : Values()
#Serializable
data class Bar(val bar: Int)
#Serializable
data class MyJson(val foo: Foo)
#Serializable
data class Foo(val version: Int, val mask: List<Values>)
No need for serializersModule:
val result = Json.decodeFromString<MyJson>(json)

spray json in scala: deserializing json with unknown fields without losing them

I'm looking for a solution to this but for json spray and my searches and attempts to get this working with json spray have failed thus far.
If I have the following json:
{
"personalData": {
"person": {
"first": "first_name",
"last": "last_name"
},
"contact": {
"phone": "1111111",
"address": {
"line": "123 Main St",
"city": "New York"
}
},
"xx": "yy", // unknown in advanced
"zz": { // unknown in advanced
"aa": "aa",
"bb": "bb",
"cc": {
"dd": "dd",
"ee": "ee"
}
}
}
}
We know for sure that the json will contain person and contact but we don't know what other fields may be created upstream from us that we don't care about/use.
I want to serialize this JSON into a case class containing person and contact but on the other hand, I don't want to lose the other fields (save them in a map so the class will be deserialized to the same json as received).
This is how far I've made it:
case class Address(
line: String,
city: String,
postalCode: Option[String]
)
case class Contact(
phone: String,
address: Address
)
case class Person(
first: String,
last: String
)
case class PersonalData(
person: Person,
contact: Contact,
extra: Map[String, JsValue]
)
implicit val personFormat = jsonFormat2(Person)
implicit val addressFormat = jsonFormat3(Address)
implicit val contactFormat = jsonFormat2(Contact)
implicit val personalDataFormat = new RootJsonFormat[PersonalData] {
def write(personalData: PersonalData): JsValue = {
JsObject(
"person" -> personalData.person.toJson,
"contact" -> personalData.contact.toJson,
// NOT SURE HOW TO REPRESENT extra input
)
}
def read(value: JsValue): CAERequestBEP = ???
}
Can someone help me do this with spray.json instead of play? I've spent such a long time trying to do this and can't seem to make it work.
In order to do that, you need to write your own formatter for PersonalDataFormat:
case class Person(first: String, last: String)
case class Address(line: String, city: String)
case class Contact(phone: String, address: Address)
case class PersonalData(person: Person, contact: Contact, extra: Map[String, JsValue])
case class Entity(personalData: PersonalData)
implicit val personFormat = jsonFormat2(Person)
implicit val addressFormat = jsonFormat2(Address)
implicit val contactFormat = jsonFormat2(Contact)
implicit object PersonalDataFormat extends RootJsonFormat[PersonalData] {
override def read(json: JsValue): PersonalData = {
val fields = json.asJsObject.fields
val person = fields.get("person").map(_.convertTo[Person]).getOrElse(???) // Do error handling instead of ???
val contact = fields.get("contact").map(_.convertTo[Contact]).getOrElse(???) // Do error handling instead of ???
PersonalData(person, contact, fields - "person" - "contact")
}
override def write(personalData: PersonalData): JsValue = {
JsObject(personalData.extra ++ ("person" -> personalData.person.toJson, "contact" -> personalData.contact.toJson))
}
}
implicit val entityFormat = jsonFormat1(Entity)
val jsonResult = jsonString.parseJson.convertTo[Entity]
The result is:
Entity(PersonalData(Person(first_name,last_name),Contact(1111111,Address(123 Main St,New York)),Map(xx -> "yy", zz -> {"aa":"aa","bb":"bb","cc":{}})))
(Assuming the json is not exactly the json above, but a valid similar one)
Code run in Scastie

Scala Play: How to map JSON array structure to Case Class

I'm completely new to Scala and Play and i stumbled upon the following the problem:
Given the following JSON structure:
[
{
"name": "Adam",
"age": 19
},
{
"name": "Berta",
"age": 22
},
...
]
I would like to map this JSON to a case classes like this:
case class User(name: String, age: Int)
case class Users(users: Seq[User])
or at least something like Seq[User].
I don't know how to traverse the JsPath because there is no key.
I tried to define an implicit read but either he cannot resolve the symbol "read" or he cannot found an implicit for user.
object User {
implicit val reads: Reads[User] = Json.reads[User]
}
object Users {
implicit val usersReads: Reads[Users] = (
(JsPath).read[Seq[User]]
)(Users.apply _)
}
How can I map my JSON to a working model?
Something like this will work
import play.api.libs.json._
case class User(name: String, age: Int)
case class Users(users: Seq[User])
object User {
implicit val reads = Json.reads[User]
}
object Users {
implicit val reads: Reads[Users] = Reads {
_.validate[Seq[User]].map(Users(_))
}
}

Kotlin: GSON fromJson doesn't compute

I'm successfully receiving a JSON Object as request and passing it on to my parser. Code runs through until I call fromJson and then gets stuck. What am I doing wrong?
Here's the corresponding class:
class User(#SerializedName("mac") private val phoneMac: String) : Comparable<User> {
#SerializedName("values")
private val measurements: MutableSet<Measurement> = mutableSetOf()
fun getPhoneMac(): String = phoneMac
fun getMeasurements(): Set<Measurement> = measurements
//etc.
}
Which refers to this class:
class Measurement (#SerializedName("mac") val deviceMac: String, val timestamp: String, val value: Double, val valueType: ValueType) : Comparable<Measurement>{
fun getDeviceMac(): String = deviceMac
fun getTimestamp(): String = timestamp
fun getValue(): Double = value
fun getValueType(): ValueType = valueType
//etc.
}
And here is how I try to parse it:
fun fromJson(json: String): User {
val builder = GsonBuilder()
builder.setPrettyPrinting()
return builder.create().fromJson(json, User::class.java)
}
Had the fromJson-function spreaded out to make sure where it gets stuck: create() still works, fromJson() doesn't
Also, I know that the JSON file is correct and doesn't contain missing values or nulls.
For verification:
{
"mac": "00-80-41-ae-fd-b1",
"values":
[
{
"mac": "ab-cd-ef-98-76-13",
"timestamp": "2012-04-23T18:25:43",
"value": 68,
"valuetype": "HR"
},
{
"mac": "ab-cd-ef-98-76-13",
"timestamp": "2012-04-23T18:35:43",
"value": 65,
"valuetype": "HR"
}
]
}
Edit: for clarification as to what I mean with my code getting stuck
For debugging purposes, I changed my fromJson function to look like this:
fun fromJson(json: String): User {
val builder = GsonBuilder()
builder.setPrettyPrinting()
println("json received")
val gson = builder.create()
println("GSON created")
val user = gson.fromJson(json, User::class.java)
println("user created")
return user
}
My Console reads
json received
GSON created
Meaning "user created" is not printed, therefore the gson.fromJson-call never returns
Not sure what you mean by getting stuck, but this seems to work:
import com.google.gson.*
import com.google.gson.annotations.*
data class User(#SerializedName("mac") val phoneMac: String, #SerializedName("values") val measurements: MutableSet<Measurement>)
enum class ValueType{
HR
}
data class Measurement (#SerializedName("mac") val deviceMac: String, val timestamp: String, val value: Double, val valuetype: ValueType)
fun fromJson(json: String): User {
val builder = GsonBuilder()
builder.setPrettyPrinting()
return builder.create().fromJson(json, User::class.java)
}
fun main() {
println(fromJson("""
{
"mac": "00-80-41-ae-fd-b1",
"values":
[
{
"mac": "ab-cd-ef-98-76-13",
"timestamp": "2012-04-23T18:25:43",
"value": 68,
"valuetype": "HR"
},
{
"mac": "ab-cd-ef-98-76-13",
"timestamp": "2012-04-23T18:35:43",
"value": 65,
"valuetype": "HR"
}
]
}
""".trimIndent()))
}
It appears the fix was to get rid of getter functions in Measurement-class or setting the fields in Measurement-class private.
I copy/pasted barsju's code, and it worked. So step by step tried to swap in pieces of my code. After swapping in my Measurments-class, I got the following exception:
java.lang.ClassFormatError: Duplicate method name "getDeviceMac" with signature "()Ljava.lang.String;" in class file jsonStuff/Measurement
Did some experimentation and research only to find that if the fields of Measurement are not private but still have getter functions explicitly declared, they're recognized as duplicate functions, basically killing the application
I would suggest, if you can, use data classes, and remove the getters and the Comparable as well.
Also that's one of the differences of the example answers some of the users gave you here.