Skip to content

Instantly share code, notes, and snippets.

@nicklockwood
Last active January 29, 2024 11:31
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nicklockwood/6b856f609224366f13bdee73d8b93d1f to your computer and use it in GitHub Desktop.
Save nicklockwood/6b856f609224366f13bdee73d8b93d1f to your computer and use it in GitHub Desktop.
Example demonstrating how to use versioning for Codable structs
// This gist demonstrates how you can implement versioning for a Codable struct to support loading
// old serialized data after changing the structure. Notable features of this solution:
//
// * No need to make new properties optional, or to perform post-processing on the struct after
// loading in ordeer to populate missing values
// * No need to change the call site - from the outside this struct behaves just the same
// as if we had implemented codable directly in the normal way.
// * Versioning can be applied individually to parents or leaves in a larger tree of
// structs without affecting the other elements
// * This approach will work even if the original struct was not designed with versioning in mind
// and was directly serialized without using an intermediate struct.
//
// Note: In this trivial case you could (and probably would) solve the problem in a simpler way
// without having multiple versions of the serialization struct by just making the `id` property
// optional, but the intention is to demonstrate how to handle an arbitrarily complex change.
import Foundation
struct User {
var name: String
var id: String
}
extension User: Codable {
// V1 used email instead of opaque id
private struct UserV1: Decodable {
let name: String
let email: String
}
// V2 matches current implementation
private struct UserV2: Decodable {
let name: String
let id: String
}
init(from decoder: Decoder) throws {
do {
let userV2 = try UserV2(from: decoder)
self.init(name: userV2.name, id: userV2.id)
} catch {
let userV1 = try UserV1(from: decoder)
self.init(name: userV1.name, id: userV1.email)
}
}
}
let oldJSON = """
{
"name": "Foo Bar",
"email": "foobar@example.com"
}
"""
let data = oldJSON.data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: data)
print(user) // User(name: "Foo Bar", id: "foobar@example.com")
@jkalias
Copy link

jkalias commented Jul 14, 2021

Typo in line 5
loading in ordeer to populate missing values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment