Using kotlinx.serialization instead of gson

In the latest branch of openrndr-template we have switched from gson to kotlinx-serialization.

What both of these libraries do is making it easy to save and load program state. Gson comes from Google and kotlinx-serialization from the developers of Kotlin.
The syntax when using the second one is a bit less verbose.

Here the official introduction:

If you want to add kotlinx-serialization it to your openrndr-template based program, take a look at the required changes in this commit:

I’m copying here the notes from that commit:

Save JSON (before)

val gson = Gson()
val json = gson.toJson(points)
File(path).writeText(json)

Save JSON (new syntax)

val json = Json.encodeToString(points)
File(path).writeText(json)

if points is a List of Point, and Point is @Serializable

Load JSON (before)

val gson = Gson()
val typeToken = object : TypeToken<List<Point>>() {}
val json = File(path).readText()
val points: List<Point> = gson.fromJson(json, typeToken.type)

Load JSON (new syntax)

val json = File(path).readText()
val poinsts = Json.decodeFromString<List<Point>>(json)

Use these imports with the new syntax

import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

OPENRNDR classes are already annotated as @Serializable so that’s taken care of. If you want to serialize your own classes, you need to annotate them. For example:

import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
...

@Serializable
data class Point(
    var position: Vector2,
    var radius: Double,
    var completeness: Double = 1.0,
    var rotation: Double = 0.0
) {
    @Transient
    var contour: ShapeContour? = null

    init {
        ...
    }
    ....

You can mark as @Transient members that do not need serialized. In this example, contour is generated out of position, radius, completeness and rotation, so there’s no need to save the contour to the JSON file, which would make it heavier and less readable. I simply reconstruct that contour inside init, or any time one of the four properties of Point are changed. The init method is called when you deserialize (load a JSON file to construct object instances).

With these annotations, one could easily load and save a list of Point with functions like these:

fun saveJson(points: MutableList<Point>) {
    File(pointsPath).writeText(Json.encodeToString(points))
    println("saved")
}

fun loadJson(): MutableList<Point> {
    return if(File(pointsPath).exists()) {
        val json = File(pointsPath).readText()
        println("loaded")
        Json.decodeFromString<MutableList<Point>>(json)
    } else mutableListOf()
}