Reputation: 29518
I'm trying to build a function that takes a Map<String, Any>
in Kotlin. In the function, I'd like to check the type and then perform logic on it depending on the type. So people could pass into the function maps that looked like:
"key": 1 (int)
"key": 1.2 (double)
"key": "someString" (string)
"key": [1, 2, 3] (array of ints)
"key": ["apple", "banana", "cutie"] (array of strings)
I want to check the type and cast it to a type so I can either use just the single value, or the array of values.
So far I can do the single values:
when (value) {
is String -> doWork(value)
is Int -> doWork(value)
is Float -> doWork(value)
is Double -> doWork(value)
is IntArray -> ???
else -> {
// ???
}
}
I'm pretty new to Kotlin, but when I call this function with
foo(mapOf("test_track" to arrayOf(1, 2, 3))
The when block drops to the else because arrayOf returns an Array<Int>
which is not an IntArray. And my end goal is to be able to check for those types Array<Int>
, Array<String>
, or even List<Int>
and if something is a Collection
, then I can use forEach
or another looping/mapping construct to get the individual elements out. But I can't seem to find a way to do that.
Upvotes: 2
Views: 1231
Reputation: 28288
arrayOf
return an Array<T>
. If you want an IntArray rather than an Array<Int>
, you're looking for intArrayOf
.
The main issue here is that an IntArray
is an int[]
, where as an Array<T>
is converted to an Integer[]. And here you can probably tell why it fails: An int[]
is not an instance of Integer[]
, and the other way around. Or at least basic conversion fails.
The observed types Array and IntArray are also different, but it's the runtime difference that backs up why it breaks.
If you intend to accept arrays like IntArray and FloatArray (primitive arrays) as well, the problem is that they have no shared super class. No Collection, no Iterable, only Any
. This means you can't do a when branch with multiple types for these.
As mentioned in the other answer though, you can use a recursive approach. And as I mentioned, the when statement excludes IntArray, FloatArray, and similar ones from multi-catch when statements. These need separate branches if you plan on processing these. Additionally, an Array isn't an Iterable either, which means you need a separate branch for it too. Fortunately, you don't need branches for each of the generic types, although you will find yourself with Any?
in the process.
// This function is just here to show what I've based this off (and make any misunderstandings visible early)
fun processMap(map: Map <String, Any>) {
for ((key, value) in map) {
/*return value(s) = */
processValue(value);
}
}
fun processValue(value: Any?) {
when (value) {
// ... other types. I'm going to assume doWork acts differently based on types, but
// if it is the same function and there are no type differences, you can
// merge them into a single branch with:
//
// is String, is Int, is Float, is Double -> doWork(value)
// ...
is String -> doWork(value)
is Int -> doWork(value)
is Float -> doWork(value)
is Double -> doWork(value)
// ...
// Process lists, collections, and other Iterables.
is Iterable<*> -> value.forEach { processValue(it) }
// Process Arrays
is Array<*> -> value.forEach { processValue(it) }
// Process primitive arrays
is IntArray -> value.forEach { processValue(it) }
is FloatArray -> value.forEach { processValue(it) }
// And so on
}
}
If you plan on processing maps, you can use is Map<*, *>
and process your elements in any way you'd like.
The separation of Array
, IntArray
, and FloatArray
is to make smart cast work. If you join them into a single branch, it breaks because it can't determine the type. This means you go back to an Any
, which doesn't automatically have an iterator()
method, which prevents both value.forEach
and for(item in value)
. The lack of a shared interface for IntArray
, FloatArray
, and similar, do not make this any easier.
Both of these produce the same instance though, so i.e. Array<Int>.forEach
and IntArray.forEach
would both trigger the is Int
branch of your when statement.
Upvotes: 4
Reputation: 77
What you can do is iterate through the elements of an Iterable and process the elements as well.
fun doAction(value: Any?)
{
when (value)
{
is Iterable<*> -> {
value.forEach {
doAction(it)
}
}
}
}
This means that you cant really do a special action for Array but you can process all of its elements.
This might be a viable solution to your scenario.
Upvotes: 4