1

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.

6 Answers 6

4

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();
Sign up to request clarification or add additional context in comments.

1 Comment

It is really good that ArangoDB team made progress towards the Jackson community. But this example failed for me. I made a minimal example project on GitHub to demonstrate my issue. Seems like this serializer breaks the authentication phase of the ArgnoDB Java asynchronous driver.
2

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.

2 Comments

Yep, I read the doc before the question as registering hundreds of serializers is no fun. Just wanted a generic solution like with Jackson Kotlin module. Either way, this is part of the Vert.x project so I already have a com.fasterxml.jackson.module:jackson-module-kotlin dependency.
Speaking of which, one other solution would be to reverse engineer jackson-module-kotlin and write analogous one for VelocyPack
0

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?

Comments

0

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
)

Comments

0

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

Comments

0

fixed since java-velocypack 2.2.0: https://github.com/arangodb/java-velocypack#kotlin-data-classes

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.