简体   繁体   中英

ArangoDB Java Driver with Kotlin data classes

Well, Arongo DB Java driver has no problems to store Kotlin data classes but it cannot load them back.

Showcase:

import com.arangodb.ArangoCollection
import com.arangodb.ArangoDB
import com.arangodb.entity.DocumentCreateEntity

fun main(args: Array<String>) {
    // Get or recreate collection: "some_collection" in DB "test_db"
    val collection: ArangoCollection = with(ArangoDB.Builder().build()!!.db("test_db")) {
        if (!exists()) create()
        with(collection("some_colelction")) {
            if (!exists()) create()
            this
        }
    }

    // POJO as Kotlin data class
    data class Foo(
            val topic: String,
            val answer: Int
    )

    val result: DocumentCreateEntity<Foo> = collection.insertDocument(Foo("The ultimate answer", 42))

    // reusult have a key of the new document
    // And in ArangoDB Web Interface you can see this document: {"answer":42,"topic":"The ultimate answer"}
    // http://localhost:8529/_db/test_db/_admin/aardvark/index.html#collection/some_colelction/documents/

    // But it doesn't work backwards
    val foo: Foo = collection.getDocument(result.key, Foo::class.java)
}

Stacktrace:

Exception in thread "main" com.arangodb.ArangoDBException: com.arangodb.velocypack.exception.VPackParserException: java.lang.InstantiationException: MainKt$main$Foo
    at com.arangodb.internal.util.ArangoDeserializerImpl.deserialize(ArangoDeserializerImpl.java:59)
    at com.arangodb.internal.util.ArangoUtilImpl.deserialize(ArangoUtilImpl.java:58)
    at com.arangodb.internal.ArangoExecutor.createResult(ArangoExecutor.java:112)
    at com.arangodb.internal.ArangoExecutorSync$1.deserialize(ArangoExecutorSync.java:56)
    at com.arangodb.internal.ArangoExecutorSync.execute(ArangoExecutorSync.java:72)
    at com.arangodb.internal.ArangoExecutorSync.execute(ArangoExecutorSync.java:53)
    at com.arangodb.internal.ArangoExecutorSync.execute(ArangoExecutorSync.java:49)
    at com.arangodb.internal.ArangoCollectionImpl.getDocument(ArangoCollectionImpl.java:134)
    at com.arangodb.internal.ArangoCollectionImpl.getDocument(ArangoCollectionImpl.java:126)
    at MainKt.main(main.kt:30)
Caused by: com.arangodb.velocypack.exception.VPackParserException: java.lang.InstantiationException: MainKt$main$Foo
    at com.arangodb.velocypack.VPack.deserialize(VPack.java:398)
    at com.arangodb.internal.util.ArangoDeserializerImpl.deserialize(ArangoDeserializerImpl.java:55)
    ... 9 more
Caused by: java.lang.InstantiationException: MainKt$main$Foo
    at java.lang.Class.newInstance(Class.java:427)
    at com.arangodb.velocypack.VPack.createInstance(VPack.java:488)
    at com.arangodb.velocypack.VPack.deserializeObject(VPack.java:450)
    at com.arangodb.velocypack.VPack.getValue(VPack.java:569)
    at com.arangodb.velocypack.VPack.deserialize(VPack.java:396)
    ... 10 more
Caused by: java.lang.NoSuchMethodException: MainKt$main$Foo.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.newInstance(Class.java:412)
    ... 14 more

Kotlin's data classes nicely serialized into expected JSON documents but seems like ArangoDB Java driver cannot load them back. If I get the document as BaseDocument I have no problems to map it back to my data class using some JSON library but come on! There must be a better way or I definitely missed something.

The ArangoDB Java driver supports alternative serializer to de-/serialize documents, edges and query results. One implementation is VelocyJack which is based on Jackson working with jackson-dataformat-velocypack .

You should be able to configure it, that it is working together with the jackson-kotlin-module .

VelocyJack velocyJack = new VelocyJack();
velocyJack.configure((mapper) -> {
  mapper.registerModule(new KotlinModule())
});
ArangoDB arango = new ArangoDB.Builder().serializer(velocyJack).build();

ArangoDB uses its own serialization framework - VelocyPack - to serialize and deserialize classes. As you can see in the code (and in the stacktrace you provided) that framework needs parameterless constructor to create an instance of deserialized class, which Kotlin's data classes do not have. As far as I know VelocyPack does not have a module for working with Kotlin data classes (like Jackson does), so your only option would be to create custom deserializer for your class and register it - it's possible with VelocyPack (see documentation ), so I assume it's also possible in ArrangoDB.

Edit: Class ArrangoDB.Builder has method registerDeserializer(final Class<T> clazz, final VPackDeserializer<T> deserializer) , which I assume can be used to register custom JSON deserializers for VelocyPack.

As a workaround for the vert.x project with com.fasterxml.jackson.module:jackson-module-kotlin dependency you can add a custom inlined extension function with a reified type so it will generically extract hashmap of the document then let Jackson's Kotlin module to do the magic:

inline fun <reified T> ArangoCollection.getDoc(key: String): T =
        JsonObject(getDocument(key, BaseDocument::class.java).properties).mapTo(T::class.java)!!

Then this line works with type inferring by the Kotlin compiler:

val for: Foo = collection.getDoc("document-key")

Known issues:

  • Does not consider ArangoDB document native properties like: _id , _key , _rev , _from , _to
  • Seems like Jackson still have issues with anonymous classes

Any ideas on how to improve it or how to reduce conversion overhead?

You have to make the fields in your data class mutable by using var instead of val . Val means the fields are final.

Next you need a parameterless constructor. You can achieve this by setting default values for the fields in your constructor or setting them null by default. If you decide to set them null you have to remove the Null safety from your fields by adding a '?' behind the data types.

More on removing Null Safety: https://kotlinlang.org/docs/reference/null-safety.html

Final data class with defaults:

data class Foo(
    var topic: String = "",
    var answer: Int = ""
)

Final data class with null:

data class Foo(
    var topic: String? = null,
    var answer: Int? = null
)

In your code you should use:

arango.setSerializer(VelocyJack...

instead of:

arango.serializer(VelocyJack...

otherwise you only use it for serializing and not for deserializing.

I created a pull request which you can use as workaround, using the kotlin no-arg maven plugin, here: https://github.com/slavaatsig/arangodb-jackson-dataformat-velocypack-issue/pull/1/files

Even if the jackson KotlinModule works with data classes (as I verified here: https://github.com/rashtao/arangodb-jackson-dataformat-velocypack-issue/blob/dbg_serde/src/main/kotlin/com/example/issue/main.kt#L16-L21 ), somehow the driver tries accessing the no-arg constructor...

For further updates on this issue: https://github.com/arangodb/arangodb-java-driver/issues/202

java-velocypack 2.2.0开始java-velocypack 2.2.0https ://github.com/arangodb/java-velocypack#kotlin-data-classes

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM