xxfast / KStore

A tiny Kotlin multiplatform library that assists in saving and restoring objects to and from disk using kotlinx.coroutines, kotlinx.serialisation and kotlinx.io
https://xxfast.github.io/KStore/
Apache License 2.0
490 stars 15 forks source link

How to handle migrations? #8

Closed Benjiko99 closed 1 year ago

Benjiko99 commented 1 year ago

If I have stored an object and need to alter the structure of the class, how can I migrate the existing data to the new structure? Do you support this?

Some users might take a long time to download the latest update of an app, I must thus be able to migrate between from e.g. v1 to v5 of a data structure. Do you support this?

xxfast commented 1 year ago

Hi @Benjiko99

Migration is not yet supported - the store will return null if it detects a malformed file. You could write your own custom decoder to do any migrations but this is not something the store does for you.. for now.

I will keep this issue open 😊 watch this issue for updates

xxfast commented 1 year ago

Hi @Benjiko99 I got a WIP for in #9.

Migrating stores

If the new models are binary compatible, you can use the existing fields to derive the new fields without needing to write your own migrations

@Serializable data class CatV1(val name: String, val lives: Int = 9)
@Serializable data class CatV2(val name: String, val lives: Int = 9, val age: Int = 9 - lives)

Binary incompatible changes

If the models are not binary-compatible, you will need to specify how to migrate the models from version to version

@Serializable data class CatV1(val name: String, val lives: Int = 9, val cuteness: Int) 
@Serializable data class CatV2(val name: String, val lives: Int = 9, val age: Int = 9 - lives, val kawaiiness: Long)
@Serializable data class CatV3(val name: String, val lives: Int = 9, val age: Int = 9 - lives, val isCute: Boolean)

private val storeV3: KStore<CatV3> = storeOf(filePath = filePath, version = 3) { version, jsonElement ->
  when (version) {
    1 -> jsonElement?.jsonObject?.let {
      val name = it["name"]!!.jsonPrimitive.content
      val lives = it["lives"]!!.jsonPrimitive.int
      val age = it["age"]?.jsonPrimitive?.int ?: (9 - lives)
      val isCute = it["cuteness"]!!.jsonPrimitive.int.toLong() > 1
      CatV3(name, lives, age, isCute)
    }

    2 -> jsonElement?.jsonObject?.let {
      val name = it["name"]!!.jsonPrimitive.content
      val lives = it["lives"]!!.jsonPrimitive.int
      val age = it["age"]?.jsonPrimitive?.int ?: (9 - lives)
      val isCute = it["kawaiiness"]!!.jsonPrimitive.long > 1
      CatV3(name, lives, age, isCute)
    }

    else -> null
  }
}

Let me know if this API fulfils your use case

xxfast commented 1 year ago

Feature added in 0.2.0 🙌 🚢