0

I am getting the following error whilst trying to map an incoming REST-api result in Json to an object :

failed to invoke private kotlinx.serialization.json.jsonelement() with no args

After some searching the problem seems to be that I am trying to instantiate a Value() object whilst it's a sealed class. Now I'm not sure how to solve this ?

data class Device (
    @SerializedName(value="DeviceSettings")
    val deviceSettings: Map<String, Value>? = null,
    @SerializedName(value="Id")
    val id: String
)

sealed class Value {
    class BoolValue(val value: Boolean)  : Value()
    class DoubleValue(val value: Double) : Value()
    class IntegerValue(val value: Long)  : Value()
    class StringValue(val value: String) : Value()
}

The problem occurs when the deviceSettings mapping is being constructed. Because it might contain several types of data the Value sealed class is defined. But as mentioned this will simply crash whilst beings parsed...

The idea is that the DeviceSettings Map is dynamic, that's why it's not actually parsed into a predefined data class for example ! It might contain a single key:value pair but there might also be thirty !

@cutiko Thats correct, Hereby some of the received json :

"DeviceSettings": {
    "ToneLevel": 80.0,
    "AutoLogging": false,
    "OrientationLandscape": false,
    "AudioDuration": 2500.0,
    "ShoutLevel": 1.0,
    "SipExtension": 0.0,
    "SipServerAuthenticationKey": "",
    "SipServerPort": 5060.0,
    "SipServerUri": ""
}
Jay
  • 2,852
  • 1
  • 15
  • 28
TiGer
  • 5,879
  • 6
  • 35
  • 36
  • You have `Map`. Does that mean the JSON node `deviceSettings` is a map of `key: object` (`"key1":{"value":1}, "key2":{"value":true`)? Can you share the JSON? – cutiko May 10 '23 at 12:58
  • @cutiko I added some more info – TiGer May 10 '23 at 14:16
  • 1
    `DeviceSettings` is not a map of `key: Object` is either an object or `key:Any`. Your representation won't work because the key `value` doesn't exist and and the value of your objects are primitive data types, not an object with an attribute inside. – cutiko May 10 '23 at 14:20
  • I solved my issue by replacing Value with Any just like @cutiko hinted in his answer. I cant seem to accept it as answer being it a comment ? – TiGer May 30 '23 at 18:21

1 Answers1

0

You will have to write a custom TypeAdapter which peeks at the type of the JSON data and depending on it chooses the correct Value subclass to instantiate. For example:

object ValueTypeAdapter : TypeAdapter<Value>() {
    override fun write(writer: JsonWriter, value: Value?) {
        throw UnsupportedOperationException()
    }

    override fun read(reader: JsonReader): Value {
        return when (val token = reader.peek()) {
            JsonToken.BOOLEAN -> Value.BoolValue(reader.nextBoolean())
            JsonToken.STRING -> Value.StringValue(reader.nextString())
            JsonToken.NUMBER -> {
                // Read the string representation of the number
                val numberAsStr = reader.nextString()
                // If number contains decimal point or exponent prefix 'e', parse as Double
                if (numberAsStr.any { c -> c == '.' || c == 'e' || c == 'E' }) {
                    Value.DoubleValue(numberAsStr.toDouble())
                } else {
                    Value.IntegerValue(numberAsStr.toLong())
                }
            }
            else -> throw IllegalArgumentException("Unexpected value type $token at ${reader.path}")
        }
    }
}

You would then register the adapter on a GsonBuilder like this:

val gson = GsonBuilder()
    .registerTypeAdapter(Value::class.java, ValueTypeAdapter)
    .create()

There are however a few things to consider, so you might have to adjust this code:

  • How do you want to treat -0? With the code above it would be parsed as Long and the - sign would be lost. Maybe you want to parse it as Double -0.0 instead.
  • In your example JSON data all numbers have a decimal point; should those all be parsed as Double or do you have a different logic for deciding what is a Double or a Long?
  • The original exception message you showed seems unrelated to the problem you mentioned, at least the connection to kotlinx.serialization is not clear.
Marcono1234
  • 5,856
  • 1
  • 25
  • 43