Skip to content

Instantly share code, notes, and snippets.

@insidegui
Created January 4, 2022 18:03
Show Gist options
  • Save insidegui/15a006d76bf7c238bceb221ea21f4e3f to your computer and use it in GitHub Desktop.
Save insidegui/15a006d76bf7c238bceb221ea21f4e3f to your computer and use it in GitHub Desktop.
Property wrapper that allows for properties to be encoded as references by ID and resolved while decoding.
protocol ReferenceEncodable: Identifiable {
static var referenceStorageKey: CodingUserInfoKey { get }
}
extension ReferenceEncodable {
static var referenceStorageKey: CodingUserInfoKey {
CodingUserInfoKey(rawValue: String(describing: Self.self) + "ReferenceStorage")!
}
}
@propertyWrapper
struct CodableReference<T>: Hashable where T: ReferenceEncodable, T.ID: Codable & Hashable {
var wrappedValue: T
func hash(into hasher: inout Hasher) {
hasher.combine(wrappedValue.id)
}
static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.wrappedValue.id == rhs.wrappedValue.id
}
}
extension CodableReference: Codable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue.id)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let id = try container.decode(T.ID.self)
guard let storage = decoder.userInfo[T.referenceStorageKey] as? [T] else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Couldn't find reference storage for type \(String(describing: T.self)), decoder is missing a userInfo key \(T.referenceStorageKey.rawValue) of type [\(String(describing: T.self))]")
}
guard let concreteValue = storage.first(where: { $0.id == id }) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Couldn't find \(String(describing: T.self)) with id \(id)")
}
self.init(wrappedValue: concreteValue)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment