Why can't I cast an Integer to a Long in Kotlin? - kotlin

It seems this should be safe to do?
#Test
fun testCast() {
val storeId : Any = Int.MAX_VALUE
val numericStoreId = if(storeId is String) storeId.toLong() else storeId as Long
}
This yields:
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Long
Why is Kotlin not allowing this? Our Kotlin version is 1.6.
The fix is writing val numericStoreId = if(storeId is String) storeId.toLong() else (storeId as Number).toLong() instead, but I don't get why it is required.

You can't do it in Java either. They simply aren't the same types.
What you're trying to do is equivalent to this:
Integer i = 0;
Long l = (Long) i;

Related

Why does Moshi parse integers, longs as Double?

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?
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
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?

as Int vs toInt(), what's the difference between the two?

I have the question
fun main(args : Array<String>){
val aa = "1"
val bb = aa.toInt() // <----- no problem
println(bb)
var cc = "1"
var dd = cc as Int // <----- exception
println(dd)
}
if I use as then what happens is...
Compiler : Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at MainKt.main(main.kt:7)
as Int casts something which is already an Int to the type Int.
val x:Any = 5
val xInt = x as Int
.toInt() parses a string that represents an Int.

Kotlin String to Int or zero (default value)

How can I covert String to Int in Kotlin and if it can't be then return 0 (default value).
I think the best solution is to tell value is Int and use Elvis operator to assign value 0 if it can't be converted.
val a:String="22"
val b:Int = a.toIntOrNull()?:0//22
val c:String="a"
val d:Int = c.toIntOrNull()?:0//0
To make code more concise you can create Extension Function
fun String?.toIntOrDefault(default: Int = 0): Int {
return this?.toIntOrNull()?:default
}

How to convert a Data Class to ByteBuffer in Kotlin?

I am trying to use Kinesis, which expects data in byte buffer format. All the examples I have seen so far are in Java and pass simple strings.
Can anybody give an idea of how to convert a kotlin data class to bytebuffer?
e.g.
data class abc (
var a: Long,
var b: String,
var c: Double
)
Check the below method
fun toByteArray(): ByteArray? {
val size: Int = 8 + 8 + string.Size
val byteBuffer = ByteBuffer.allocate(size)
.put(long) //long veriable
.put(double) // double veriable
.put(string)
return byteBuffer.array()
}
You can allocate the size based on dataType size like Int 4 bytes, Double and Long 8 bytes
for reading back to dataType
val byteBuffer = ByteBuffer.wrap(byteArray)
byteBuffer.get(Int) //Int variable
byteBuffer.get(Double) //Double variable
byteBuffer.get(nonce)
You might want to have a look at kotlinx.serialization. It is an official Kotlin project and supports several formats out-of-the-box. You can use the output and wrap it in with ByteBuffer.wrap
Thanks for all the suggestions.
Solved the problem using ObjectMapper() of Jackson library (jackson-databind) and annotations.
Following code used for serialization:
val objectMapper = ObjectMapper()
objectMapper.registerModule(JavaTimeModule())
val buf = ByteBuffer.wrap(objectMapper.writeValueAsString(className).toByteArray(Charsets.UTF_8))
code for deserialization:
val objectMapper = ObjectMapper()
objectMapper.registerModule(JavaTimeModule())
val obj = objectMapper.readValue(Charsets.UTF_8.decode(record.data()).toString(), ClassName::class.java)
Apart from this, I had to add constructors of all the data classes and had to add the following annotation to all the LocalDateTime attributes:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
#JsonFormat(pattern = "YYYY-MM-dd HH:mm")
var edd: LocalDateTime?,
A simple solution with no additional libraries
Note: must be tailored for each data class
data class TimerConfig(val startTime: Long, val repeatCount: Int, val sequenceDuration: Int)
Converting the data class to a ByteArray
private fun TimerConfig.toByteArray(): ByteArray {
val byteBuffer = ByteBuffer.allocate(Long.SIZE_BYTES + Int.SIZE_BYTES + Int.SIZE_BYTES)
byteBuffer.putLong(this.startTime)
byteBuffer.putInt(this.repeatCount)
byteBuffer.putInt(this.sequenceDuration)
return byteBuffer.array()
}
Recovering the data class from the received ByteArray
private fun ByteArray.toTimerConfig(): TimerConfig {
val byteBuffer = ByteBuffer.wrap(this)
return TimerConfig(byteBuffer.long, byteBuffer.int, byteBuffer.int)
}

Kotlin JS - string to number conversion?

How to do String to number conversion in Kotlin JS app. I am Using the following code and having some trouble converting from the HTMLInputElement value to double.
fun c2f(self: Any) {
console.log("Self object: ${self} ")
val celsius = document.getElementById("celcius")?.getAttribute("value") as Double
val fahrenheit = celsius * 1.8 + 32
console.log("Fahrenheit value: ${fahrenheit} ")
window.alert("Celcius (${celsius}) -> Fahrenheit (${fahrenheit}) ")
}
Also i am not seeing any toDouble() function on String class as in the case of JVM app.
Answering my own question as this would be helpful for somebody.
You can use the kotlin.js top level parse functions for string <-> Number conversion.
fun parseInt(s: String, radix: Int = 10): Int
fun safeParseInt(s : String) : Int?
fun safeParseDouble(s : String) : Double?
Simply use the String.toXXX() functions, e.g.
val n = "1"
val m = 2 + n.toInt()
val x = "1.1"
val y = 2.0 + x.toFloat()
etc.