dictionarykotlinmultidimensional-arraymutablemap

How to set value to the child map in the father map in kotlin?


as title, I have the code:

val description= mutableMapOf(
    "father2" to "123",
    "father" to mutableMapOf<String,String>())
description["father"]["child"] = "child value"

if i try this: description["father"]["child"]="child value"

I will get the error:

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
public inline operator fun <K, V> MutableMap<String!, String!>.set(key: String!, value: String!): Unit defined in kotlin.collections
public inline operator fun kotlin.text.StringBuilder /* = java.lang.StringBuilder */.set(index: Int, value: Char): Unit defined in kotlin.text

how can i do?


Solution

  • This is about typing (or lack of it).

    The description map is created from two pairs, one from a String to a String, and the other from a String to a MutableMap<String, String>.  The keys are both String, so Kotlin will infer String for the type of key.  But what about the values?  They're two completely unrelated types, with no common superclass except Any.  So the type of description is inferred to be MutableMap<String, Any>.  (You can check that in e.g. the REPL.)

    So, when you pull out a value, what can the compiler tell about it?  Almost nothing.  It's an Any, so the only thing you can be sure of is that it's not null.

    That's why the compiler isn't treating that pulled-out value like a map; as far as it knows, it might not be a map!  (We know — but only because we can see what the map was initialised with, and that it's not modified since then, nor is the reference shared with any other objects or threads that could modify it.  But that's a relatively rare circumstance.)

    Kotlin is a strongly-typed language, which means the compiler tracks the type of everything, and tries very hard not to let you do anything that could cause a runtime error.  Your code, if it compiled, would risk a ClassCastException any time the data didn't exactly match what you expected, which is hardly a route to good programs.

    So, what can you do?

    The safest thing would be to change your program so that you don't have any maps with unknown types of values.  Obviously, that depends on what your program is doing, so we can't make useful suggestions here.  But if your description were a MutableMap<String, MutableMap<String, String>>, then the compiler would know exactly what type everything was, and your intended code would compile and run fine.

    The other main alternative would be to check what the value is before trying to treat it like a map.  One way to do this uses the as? safe-cast operator.  That checks whether a value is of a given type; if so, it's cast to that type; otherwise, it returns null.  You can then use the .? safe-call operator to call a method only if the value isn't null.  Putting that together:

    (description["father"] as? MutableMap<String, String>)?.put("child", "child value")
    

    If the value is as you expect, that will work fine; if not, it will do nothing and continue.

    That's rather long-winded, but it will compile and run safely.  Alternatively, you could do the same thing in a more explicit way like this:

    val child = description["father"]
    if (child is MutableMap<String, String>))
        child["child"] = "child value"
    

    (Within the if, the compiler knows the type of child, and uses a a ‘smart cast’ to allow the putter call.)

    That's even more long-winded, of course.  But that's what you get from trying to work against the type system :-)

    (Oh, and by the way, I think the usual terminology for recursive data structures is ‘parent’ and ‘child’; I've not seen ‘father’ used that way before.)