How to migrate Codable object in Swift

Issue #83

As of swift 4 migration, we updated Cache to fully take advantage of Codable. It works for most cases, as we should usually declare our entity as typed safe object instead of array or json dictionary. And by conforming to Codable, it is easily encoded and decoded to and from json data. And persisting them to Cache is as easy as eating cookie.

The other day, I saw someone asking on how to migrate if the model changes https://github.com/hyperoslo/Cache/issues/153, and he likes the way Realm does https://realm.io/docs/swift/latest/#migrations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// The enumerateObjects(ofType:_:) method iterates
// over every Person object stored in the Realm file
migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
// combine name fields into a single field
let firstName = oldObject!["firstName"] as! String
let lastName = oldObject!["lastName"] as! String
newObject!["fullName"] = "\(firstName) \(lastName)"
}
}
})

I think we can rely on Codable to the migration. FYI, here is the PR https://github.com/hyperoslo/Cache/pull/154

Class name change

I see Codable is based on json, and the importance of json is its data structure, not the class name. So if you change the class name, it still works.

First, we save model of type Person, later we load model of type Alien. It works because the structure stays the same

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Person: Codable {
let firstName: String
let lastName: String
}

struct Alien: Codable {
let firstName: String
let lastName: String
}

let person = Person(firstName: "John", lastName: "Snow")
try! storage.setObject(person, forKey: "person")

// As long as it has same properties, it works too
let cachedObject = try! storage.object(ofType: Alien.self, forKey: "person")
XCTAssertEqual(cachedObject.firstName, "John")

Property change

If the property changes, then you need to do a little work of migration.

First, we save model of type Person1, it has just fullName. Later we change the model to Person2 with some new properties. To do the migration, we need to load model with old Person1 first, then construct a new model Person2 based on this Person1. Finally, save that to Cache with the same key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Person1: Codable {
let fullName: String
}

struct Person2: Codable {
let firstName: String
let lastName: String
}

// Firstly, save object of type Person1
let person = Person1(fullName: "John Snow")

try! storage.setObject(person, forKey: "person")
XCTAssertNil(try? storage.object(ofType: Person2.self, forKey: "person"))

// Later, convert to Person2, do the migration, then overwrite
let tempPerson = try! storage.object(ofType: Person1.self, forKey: "person")
let parts = tempPerson.fullName.split(separator: " ")
let migratedPerson = Person2(firstName: String(parts[0]), lastName: String(parts[1]))
try! storage.setObject(migratedPerson, forKey: "person")

XCTAssertEqual(
try! storage.object(ofType: Person2.self, forKey: "person").firstName,
"John"
)

Comments