How to iterate list which has been unsafeCast-ed from JSON

Hello,

I am struggling with simple for loop of list common data objects generated from JSON in Javascript.

The error I get is:

TypeError: “availableBooks.iterator is not a function”

The list is generated this way:

fun main() {
    MainScope().launch {
        val availableBooks = getAvailableBooks()
        val innerHtml = StringBuilder().append("Aplikacia je nacitana: ")

        console.log(availableBooks)
        println("${availableBooks::class}")

        for (availableBook in availableBooks) {
            innerHtml.append(availableBook.name.toString())
        }


        document.getElementById("app")?.innerHTML = innerHtml.toString()

    }
}

suspend fun getAvailableBooks(): List<Book> =
    window
        .fetch("/json/gson")
        .await()
        .json()
        .await()
        .unsafeCast<List<Book>>()

Console shows this output:

image

Thank you for any help

You cannot cast a Javascript array to List because it simply isn’t a List. You can cast it to Array instead. If you really need a List, you can call toList() after casting to Array. Otherwise, just simply iterate over the Array. If you want to, you can even skip casting altogether and just iterate over the dynamic as is.

It’s interesting — I’m seeing a good number of questions (mainly on Stack Overflow) which indicate people are failing to understand the difference between casting and conversion.

(I often end up replying along the lines of: “A cast doesn’t convert a value to the required type; a cast promises the compiler that it’s already the required type.” Which seems to help.)

I’ve never suffered that particular confusion myself (plenty of others, of course), so I’m curious as to where it might originate. Are there other languages or platforms which could lead to it, and how can we avoid it? Are there tweaks to the documentation (language and/or API) which might help?

1 Like

I think one reason are untyped languages like JS or python. My guess is that new programmers that only know those languages don’t have a good enough understanding of what a type actually is to easily see the difference between conversion and cast.
I doubt this is a mistake someone who started out with strongly type language would make.

Also in this case it doesn’t help that the distinction between list and array is so small. Functionally they are almost the same after all.

1 Like

As @Wasabi375 said, even in JS and Python most of the times real objects with an archetype are used and knowing which archetype (or class if you want) is useful to really understand what is going on underneath your interpreter.

For your specific case, the Kotlin compiler maps the Array class to JS list, while the List interface is probably implemented by an ArrayList which is actually a full blown class! Indeed you cannot cast an Array to ArrayList, you need to convert it!

Now for you use case just unsafeCast to Array<Book> and it will work. The Kotlin team made sure that the signatures of functions for Array and List where the same (that is a really nice touch :slight_smile:) !

Thank you, I have changed it to Array<Book> and now it works quite fine.

@gidds I am quite new in Kotlin with several years in PHP in background, so somewhere there is coming my confusion. PHP has types, but casting actually means converting, for example gettype((int)"some string") === "integer"

As I am working on PoC (with given limited and short time), that Kotlin is good and mature technology, Its something like jump into water, so sorry for silly questions I may ask in future :nerd_face:

I would also complete the solution, where loaded JSON data via REST are transformed (mapped) into deep Array, otherwise you can loose methods like toString()

suspend fun getAvailableBooks(): Array<Book> =
    window
        .fetch("/json/gson")
        .await()
        .json()
        .await()
        .unsafeCast<Array<Book>>()
        .map {
            Book(
                Isbn(it.isbn.value),
                BookName(it.name.value),
                Credit(it.credit.value)
            )
        }
        .toTypedArray()