IndexUpdater.toList() doesn't do a deep conversion in Java SDK

While testing CBL 3.2 changes in Kotbase, using the tests from the ObjC/Swift SDK, I discovered that these two lines fail for the Java SDK. It appears that the IndexUpdater.toList() function in the Java SDK only performs a shallow conversion to List, without converting Array and Dictionary objects to List and Map objects. Document, Dictionary, Array, and Result all do a deep conversion in their respective toMap() and toList() functions. The iOS SDK performs a deep conversion for IndexUpdater as well and doesn’t fail on these same two lines.

Thanks, I filed this issue to handle this.

1 Like

Looks like this is also an issue for the C SDK as well.

Hi Jeff,

Thank you for reporting this issue. I’ve investigated the IndexUpdater.toList() implementation and created comprehensive test cases to verify the deep conversion behavior for nested dictionaries and arrays.
I wrote test cases that specifically check whether IndexUpdater.toList() recursively converts structures to plain Java Map and List objects. The tests passed successfully, indicating that the current implementation does perform deep conversion correctly.

Could you provide more details to help us reproduce the issue?

Can you provide a minimal code example that demonstrates the shallow conversion issue you’re experiencing?

Here is a little insight of the testcase which i wrote


    val nestedDict0 = MutableDictionary().apply {
        setString("name", "Bob")
    }
    val doc0 = MutableDocument().apply {
        setDictionary("value", nestedDict0)
    }
    collection.save(doc0)

    val nestedArray1 = MutableArray().apply {
        addString("one")
        addString("two")
        addString("three")
    }
    val doc1 = MutableDocument().apply {
        setArray("value", nestedArray1)
    }
    collection.save(doc1)

    val idx = createAndVerifyIndex(collection, "vector_index", VectorIndexConfiguration("value", 300, 8).setLazy(true))
    val updater = assertNonNull(idx.beginUpdate(10))

    val values = updater.toList()

    Assert.assertTrue(values[0] is Map<*, *>)
    val value0 = values[0] as Map<*, *>
    Assert. assertEquals(doc0.getDictionary("value")?.toMap(), value0)

    Assert.assertTrue(values[1] is List<*>)
    val value1 = values[1] as List<*>
    Assert.assertEquals(doc1.getArray("value")?.toList(), value1)

Seems the link to the Swift test in my first post didn’t resolve correctly. (It doesn’t like the %2B escaped + in the URL.) This one should work.

This is the same test and lines that fail in my Kotlin Multiplatform library. This is my workaround to perform a deep conversion for JVM/Android, and similar workaround for the C SDK (native Linux/Windows). If you remove the .map { … } portion of the code, the test will fail on those two lines. Note, the Objective-C SDK does not need this workaround.

This is using Couchbase Lite 3.2.4 for all three SDKs. Maybe this has been fixed already since this release?

I added your test to my library (just slightly modified for VectorSearchTestLazy.kt):

val nestedDict0 = MutableDictionary().apply {
    setString("name", "Bob")
}
val doc0 = MutableDocument().apply {
    setDictionary("value", nestedDict0)
}
collection.save(doc0)

val nestedArray1 = MutableArray().apply {
    addString("one")
    addString("two")
    addString("three")
}
val doc1 = MutableDocument().apply {
    setArray("value", nestedArray1)
}
collection.save(doc1)

val config = VectorIndexConfiguration(expression = "value", dimensions = 300, centroids = 8)
createVectorIndex(collection, "vector_index", lazyConfig(config))
val idx = collection.getIndex("vector_index")
assertNotNull(idx)
val updater = assertNonNull(idx.beginUpdate(10))

val values = updater.toList()

assertIs<Map<*, *>>(values[0])
val value0 = values[0] as Map<String, Any?>
assertEquals(doc0.getDictionary("value")?.toMap(), value0)

assertIs<List<*>>(values[1])
val value1 = values[1] as List<*>
assertEquals(doc1.getArray("value")?.toList(), value1)

It passes with my workaround in place and fails with the workaround removed:

Java/Android SDK:

java.lang.AssertionError: Expected value to be of type <java.util.Map<*, *> (Kotlin reflection is not available)>, actual <class kotbase.Dictionary (Kotlin reflection is not available)>.

C SDK:

kotlin.AssertionError: Expected value to be of type <kotlin.collections.Map<*, *>>, actual <class kotbase.Dictionary>.

(Again passes without workaround with Objective-C SDK.)

Thank you for the detailed reproduction case and for confirming this issue across multiple SDKs. IndexUpdater.toList() does not perform deep conversion in CBL 3.2.4. The Fleece-to-Java deep conversion functionality was introduced from CBL 3.3 onwards Updating to 3.3 includes the deep conversion functionality that matches the iOS SDK behavior, and your tests should pass without the workaround. If upgrading to 3.3 is not feasible for your project timeline, we can provide a maintenance patch that backports the deep conversion fix.

Thank you. I just released an update for my CBL KMP library targeting 3.2.4. I will look to upgrade to 3.3 now after this release.

From what I see, 3.3 is only available for the Android, Objective-C, and Swift SDKs. Since my library uses the Java and C SDKs as well, I won’t be able to unify any APIs that don’t exist for all platforms. I may be able to surface APIs as platform-specific, if this makes sense. But I’ll have to review the API changes and whether I can make those changes work on the platforms that are still on 3.2.4.

Will these other platforms be updating to 3.3 in the future? My code especially utilizes the fact that the Android and Java platforms share 99% of their API to share a unified jvmCommon source set between the two platforms.

This bug will still be an issue on the Java and C SDKs in 3.2.4 as well.

Oh, I see CBL 4.0 was just released! It probably makes sense to target this version in my next update, since it was released for all platforms.

Are there APIs introduced in 3.3 that are specific to only Android and iOS?