Kotlin: GSON fromJson doesn't compute - json

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.

Related

How to use Gson to deserialize a json array string to object mode using generic class type?

Having a function for deserializing json string into object mode by providing the class type the json string should be deserialized into.
It works for most of the case
fun <T> deserializeJsonStr(jsonString: String, dataClassType: Class<T>):T? {
var ret: T? = Gson().fromJson<T>(jsonString, dataClassType)
return ret
}
like
// data modle
class TestData1 {
var userId = 0
var id = 0
var title: String? = null
var body: String? = null
}
// the json string
{
"userId": 1,
"id": 3,
"title": "the title",
"body": "the body"
}
// call the function
val clazz: Class<TestData1> = TestData1::class.java
val theTestData1 = deserializeJsonStr(jsonString, clazz)
it will return the object theTestData1 with the fields filled.
But if the json string is for json array:
[
{
"userId": 1,
"id": 1,
"title": "title1",
"body": "body1"
},
{
"userId": 1,
"id": 2,
"title": "title2",
"body": "body2"
},
{
"userId": 1,
"id": 3,
"title": "title3",
"body": "body3"
}
]
Though it should be ArrayList<TestData1>, but what is it class type? tried:
val clazz: Class<ArrayList<TestData1>> = ArrayList<TestData1>::class.java
val theTestData1 = psreJsonStr(jsonString, clazz)
but it does not compile:
or the val pojoClass: Class<ArrayList<TestData1>> = ArrayList<>::class.java does not compile either:
what would be the data class mode for this json array? or what is the Class<T> required by the function param for ArrayList?
This is related to type erasure: ArrayList<TestData1>::class.java is not possible because at runtime the type argument of ArrayList is not available, so this would basically be Class<ArrayList>1. Therefore the compiler does not allow this because it would not be safe.
Gson has its TypeToken class to work around this, see also the user guide.
In your case you could change your deserializeJsonStr to the following:
fun <T> deserializeJsonStr(jsonString: String, dataType: TypeToken<T>): T? {
// Note: Older Gson versions might require using `dataType.type`
return Gson().fromJson(jsonString, dataType)
}
You would then call the function like this:
val type = object: TypeToken<ArrayList<TestData1>>() {}
val data = deserializeJsonStr(jsonString, type)
You could additionally add an overload of deserializeJsonStr with reified type parameter which makes it make more convenient to use:
inline fun <reified T> deserializeJsonStr(jsonString: String): T? {
return deserializeJsonStr(jsonString, object: TypeToken<T>() {})
}
// Note: You can even omit the explicit `ArrayList<TestData1>` if the compiler can infer it
val data = deserializeJsonStr<ArrayList<TestData1>>(jsonString, type)
1 Class is a bit special because its direct type argument is not erased at runtime, for example Class<ArrayList> is not erased to Class<Object>. But for every other generic type the type argument is erased.

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)

How to convert a JSON string to an Object in KMM

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

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

class com.google.gson.internal.LinkedTreeMap cannot be cast to class Partner

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
fun main() {
val jsonString: String = """{
"jsonrpc": "2.0",
"id": null,
"result": [
{
"id": 1,
"name": "Lekhnath Rijal"
},
{
"id": 2,
"name": "Administrator"
}
]
}"""
val body1 = Gson().fromJson<RpcResult<List<Partner>>>(jsonString, object: TypeToken<RpcResult<List<Partner>>>(){}.type)
println(body1.result[0].name) // prints Lekhnath Rijal // - As expected
val body2 = fromJson<RpcResult<List<Partner>>>(jsonString)
println(body2.result[0].name) // throws Exception as stated below after this code snippet
}
fun <T> fromJson(json: String?): T {
return Gson().fromJson<T>(json, object: TypeToken<T>(){}.type)
}
data class RpcResult<T>(
val jsonrpc: String,
val id: Int?,
val result: T
)
data class Partner(
val id: Int,
val name: String
)
Exception: java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class RpcResult
while converting json string to data class object without using function it works as expected but executing same code from helper function does not work and instead throws an exception mentioned above. What am I missing here?
It is due to type erasure in runtime. In Kotlin you can solve this issue by making your function inline with reified type:
Change your function from:
fun <T> fromJson(json: String?): T {
return Gson().fromJson<T>(json, object: TypeToken<T>(){}.type)
}
To:
inline fun <reified T> fromJson(json: String?): T {
return Gson().fromJson<T>(json, object: TypeToken<T>(){}.type)
}
For further reading check this out: https://kotlinlang.org/docs/reference/inline-functions.html