Skip to content

Serialization library that handles circular references and more with just annotations

License

Notifications You must be signed in to change notification settings

subchannel13/IkSerialization

Repository files navigation

IkSerialization

GitHub Actions Workflow Status GitHub Sponsors

Serialization library that handles circular references and cross module polymorphism with just annotations.

@SerializableClass
abstract class BaseClass(
    @SerializationData val foo: String
)

@SerializableClass
class DerivedClass(
    foo: String,
    @SerializationData val bar: String
): BaseClass(foo)

How?

The library provides KSP based compiler plugin that scans for @SerializableClass and @SerializationData annotations and generates serialization code tailor-made for the annotated classes and properties.

In a way, the objects are not serialized by reference rather than value. Each object gets assigned a name (anchor) and those name are used in place of object references. This allows not only to serialize objects with circular references but to also fully restore object relations after deserialization (no object duplication in deserialization if two objects used to reference the same third object). As a consequence serializations results with a list of objects, not just one object.

Internally deserialization works in two steps to in order restore original object relationships. First objects get instantiated with a minimum of data required to call their constructors and then they get hydrated with the rest of data.

Additionally serialization and deserialization work in two stages Packing stage will generate

Examlpe

Serialization works in two stages: packing and serialization proper. Packing stage converts concrete objects to generic maps and lists that can then be serialized to a format of choice.

// Object to serialize
val obj = ObjectSample(10)

// Packing to generic maps and lists
val session = PackingSession()
val reference = ObjectSample_Packer().pack(obj, session)

val json = JSONArray()
// Reference to the root object
json.put(serializationMapper(reference))
// Serialization of all relevant objects to JSON
session.referencedData.forEach {
    json.put(serializationMapper(it))
}

To start serialization you need a fresh instance of PackingSession and call pack on and an appropriate packer class (generated for @SerializableClass annotated class). This will populate the packing session and return the reference of the "root" object. Then you can use those data to create output in the format of choice. This does move require from library user to write that final piece of logic, but that is not as complex as it sounds. Here is an example of serializationMapper function for converting packed objects to JSON for Android:

fun serializationMapper(obj: Any, path: String = ""): Any = when(obj) {
    is Map<*, *> -> JSONArray().apply {
        put("map")
        obj.forEach {
            put(serializationMapper(it.key!!, path + ":" + it.key!!.toString()))
            put(serializationMapper(it.value!!, path + "." + it.key!!.toString()))
        }
    }
    is Iterable<*> -> JSONArray().apply {
        put("list")
        obj.forEachIndexed { i, it -> put(serializationMapper(it!!, "$path[$i]")) }
    }
    is ReferencePointer -> JSONObject().apply {
        put("ref", obj.name)
    }
    is ReferenceAnchor -> JSONObject().apply {
        put("ref", obj.pointer.name)
        put("data", serializationMapper(obj.value, "$path[${obj.pointer.name}]"))
    }
    is Double ->
        if (obj.isNaN())
            throw Exception("NaN at $path = $obj")
        else
            obj
    is Float ->
        if (obj.isNaN())
            throw Exception("NaN at $path = $obj")
        else
            obj
    else -> obj
}

About

Serialization library that handles circular references and more with just annotations

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

No packages published

Languages