Skip to content

Instantly share code, notes, and snippets.

@Noobish1
Last active September 23, 2019 20:48
Show Gist options
  • Save Noobish1/0d3c7593b1ca441bb77db930218d8d35 to your computer and use it in GitHub Desktop.
Save Noobish1/0d3c7593b1ca441bb77db930218d8d35 to your computer and use it in GitHub Desktop.
Migrating codable models
import Foundation
protocol MigratableObjectProtocol: Codable {
associatedtype PreviousObject: Codable
init(previousObject: PreviousObject)
}
import Foundation
enum Migration<Left, Right: MigratableObjectProtocol> where Right.PreviousObject == Left {
static func migrate(from data: Data) throws -> Data {
let decoder = JSONDecoder()
let encoder = JSONEncoder()
let old = try decoder.decode(Left.self, from: data)
let new = Right(previousObject: old)
return try encoder.encode(new)
}
}
import Foundation
protocol MigratableObjectVersionProtocol: RawRepresentable, CaseIterable, Equatable {
var migrationToNextVersion: (Data) throws -> Data { get }
}
import Foundation
protocol VersionedObjectProtocol: Codable {
associatedtype Version: MigratableObjectVersionProtocol where Version.RawValue == String
}
import Foundation
enum Migrator {
static func migrate<To: VersionedObjectProtocol, From: Codable>(from: From, version: To.Version, toType: To.Type) throws -> Codable {
let oldData = try JSONEncoder().encode(from)
let versions = To.Version.allCases
let fromIndex = versions.firstIndex(of: version)!
let neededMigrations = versions[fromIndex...].map { $0.migrationToNextVersion }
let newData = try neededMigrations.reduce(oldData) { data, closure in
try closure(data)
}
return try JSONDecoder().decode(Person_v2.self, from: newData)
}
}
struct Person_v1: Codable {
let firstName: String
let lastName: String
}
struct Person_v2: MigratableObjectProtocol, VersionedObjectProtocol {
typealias Version = PersonVersion
let firstName: String
let middleName: String
let lastName: String
init(previousObject: Person_v1) {
self.firstName = previousObject.firstName
self.middleName = "Unknown"
self.lastName = previousObject.lastName
}
}
enum PersonVersion: String, MigratableObjectVersionProtocol {
case v1
case v2
var migrationToNextVersion: (Data) throws -> Data {
switch self {
case .v1: return Migration<Person_v1, Person_v2>.migrate
case .v2: return { $0 }
}
}
}
let v1_person = Person_v1(firstName: "Jar Jar", lastName: "Binks")
// { firstName: "Jar Jar", lastName: "Binks" }
let v2_person = try! Migrator.migrate(from: v1_person, version: .v1, toType: Person_v2.self)
// { firstName: "Jar Jar", middleName: "Unknown", lastName: "Binks" }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment