DEV Community

Khoa Pham
Khoa Pham

Posted on

Migrating Codable object

Original post https://github.com/onmyway133/blog/issues/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


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)"
          }
      }
  })
Enter fullscreen mode Exit fullscreen mode

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

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")
Enter fullscreen mode Exit fullscreen mode

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.

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"
)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)