3

I'm trying to parse a not very well designed api's json using Moshi + kotlin. For some reasons it parses numbers like 71 as Double.

The 3rd party api has a list of objects that could either look like: {"foo":[[1234567000,12]]} // long, int or {"foo":[[1234567000,"string",0,2]]} // long, string, int, int

Because of the 3rd party api I have the following kotlin class:

@JsonClass(generateAdapter = true)
class D {
    var foo: List<Any> // I use Any because it can be either String or Int or Long
}

and in my code I do something like:

val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(D::class.java)
var D d = adapter.fromJson("{\"foo\":[[1234567000,\"string\",0,2]]}")
var index = d.foo[2]
var value : Long = 0
// here I get an error: ClassCastException: java.lang.Double cannot be cast to java.lang.Long
value = d.foo[index]

but for some reason Moshi converts the integers in the json string into Doubles instead of Int or Long. How could I fix it?

Gavriel
  • 18,880
  • 12
  • 68
  • 105
  • Can you just convert it? https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/to-long.html `value = d.foo[index].toLong()` – dominicoder Jul 18 '21 at 02:20
  • I did not try that, but since the longs in my case are timestamps I wouldn't want to risk getting a rounded value. – Gavriel Jul 18 '21 at 10:07
  • I'd expect that a long value being treated as a double being cast back to a long would not have rounding errors. I.E. 123456789 would be 123456789.0 which would "round" back to 123456789. You should be able to prove this with unit tests. – dominicoder Jul 18 '21 at 17:32

2 Answers2

1

I'm not sure if this is the easiest way but it works:

class AnyAdapter {
    @FromJson fun fromJson(str: String): Any {
        var any: Any
        try {
            any = Integer.parseInt(str)
        } catch (e: NumberFormatException) {
            try {
                any = java.lang.Long.parseLong(str)
            } catch (e: NumberFormatException) {
                try {
                    any = java.lang.Double.parseDouble(str)
                } catch (e: NumberFormatException) {
                    any = str
                }
            }
        }
        return any
    }
}
val moshi = Moshi.Builder()
        .add(AnyAdapter())
        .build()
val adapter = moshi.adapter(D::class.java)
var D d = adapter.fromJson("{\"foo\":[[1234567000,\"string\",0,2.0]]}")

var l : Long = d.foo[0] as Long
var s : String = d.foo[1] as String
var i : Int = d.foo[2] as Int
var dd : Double = d.foo[3] as Double
Gavriel
  • 18,880
  • 12
  • 68
  • 105
  • 1
    What happens if you receive a string value which actually looks like a number? – mightyWOZ Jul 18 '21 at 04:11
  • yes, that's right, for the very general case, but it works for the api I'm using, since there the strings are statuses like "MEASURED", "GUESSED". Also you could argue that the number 1 might need to be Long and not Int, but that's fine, because I convert it later. Actually I'll update the last 4 lines of my answer... – Gavriel Jul 18 '21 at 10:03
0

JSON number type makes no distinction between integer and floating-point

Fundamental idea behind any JSON parsing library is to parse JSON into certain type, if that type has properties of type integer then parsing library will try to convert JSON number type to integer, but you are parsing json to Any, which essentially tells moshi to take a guess as to the type of the Object.

Since JSON doesn't distinguish between integer and floating point fields moshi defaults to Float/Double for numeric fields when parsing to Any.

And the issue here is in the API, it should not return different type values for same query. at the very least there should be an indication as to the type of data. What happens if you receive a string value which actually looks like a number?

mightyWOZ
  • 7,946
  • 3
  • 29
  • 46
  • 2
    well, ok, I understand, though I think Moshi could be a little bit more "clever" or "brave". For example I wouldn't convert a number that doesn't have a dot in it to Double. – Gavriel Jul 18 '21 at 10:05