Last active
June 11, 2024 10:12
-
-
Save IanKeen/4f7717d7afd266137042b86983d04bc1 to your computer and use it in GitHub Desktop.
PropertyWrapper: CustomKeyCodable allows defining the keys for decoding _per property_
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protocol CustomKeyCodable: Codable { | |
init() | |
} | |
extension CustomKeyCodable { | |
init(from decoder: Decoder) throws { | |
self.init() | |
let container = try decoder.container(keyedBy: AnyCodingKey.self) | |
let mirror = Mirror(reflecting: self) | |
for (label, value) in mirror.children { | |
guard let label = label else { | |
throw DecodingError.keyNotFound(AnyCodingKey(""), .init(codingPath: container.codingPath, debugDescription: "Not an KeyedContainer")) | |
} | |
guard let key = value as? KeyType else { | |
throw DecodingError.keyNotFound(AnyCodingKey(label), .init(codingPath: container.codingPath, debugDescription: "All keys must use @Key property wrapper")) | |
} | |
try key.decode(container: container, key: key.name ?? String(label.dropFirst())) | |
} | |
} | |
func encode(to encoder: Encoder) throws { | |
var container = encoder.container(keyedBy: AnyCodingKey.self) | |
let mirror = Mirror(reflecting: self) | |
for (label, value) in mirror.children { | |
guard let label = label else { | |
throw EncodingError.invalidValue(self, .init(codingPath: container.codingPath, debugDescription: "Not an KeyedContainer")) | |
} | |
guard let key = value as? KeyType else { | |
throw EncodingError.invalidValue(value, .init(codingPath: container.codingPath, debugDescription: "All keys must use @Key property wrapper")) | |
} | |
try key.encode(container: &container, key: key.name ?? String(label.dropFirst())) | |
} | |
} | |
} | |
private protocol KeyType { | |
var name: String? { get } | |
func decode(container: KeyedDecodingContainer<AnyCodingKey>, key: String) throws | |
func encode(container: inout KeyedEncodingContainer<AnyCodingKey>, key: String) throws | |
} | |
extension Key: KeyType {} | |
@propertyWrapper | |
public final class Key<T: Codable>: Codable { | |
public var name: String? | |
public var wrappedValue: T { | |
get { | |
guard let value = value else { fatalError("Unable to access value before it is set") } | |
return value | |
} | |
set { value = newValue } | |
} | |
private var value: T? | |
public init() { } | |
public init(_ name: String) { | |
self.name = name | |
} | |
public init(wrappedValue: T) { | |
self.value = wrappedValue | |
} | |
public init(wrappedValue: T, _ name: String) { | |
self.name = name | |
self.value = wrappedValue | |
} | |
func decode(container: KeyedDecodingContainer<AnyCodingKey>, key: String) throws { | |
self.name = key | |
self.value = try container.decode(T.self, forKey: AnyCodingKey(key)) | |
} | |
func encode(container: inout KeyedEncodingContainer<AnyCodingKey>, key: String) throws { | |
try container.encode(value, forKey: AnyCodingKey(key)) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Note: All properties must be marked with `@Key` | |
// provide a value if you are specifying a key, otherwise you can omit it | |
struct User: CustomKeyCodable { | |
@Key("first_name") var firstName: String | |
@Key("last_name") var lastName: String | |
@Key var age: Int | |
init() { } | |
} | |
let json = Data(#"{"first_name":"Ian", "last_name": "Keen", "age":42}"#.utf8) | |
var model = try! JSONDecoder().decode(User.self, from: json) | |
print(model.firstName) // Ian | |
print(model.lastName) // Keen | |
print(model.age) // 42 | |
try print(String(data: JSONEncoder().encode(model), encoding: .utf8)!) | |
//{"age":42,"first_name":"Ian","last_name":"Keen"} | |
model.lastName = "Smith" | |
model.age = 123 | |
try print(String(data: JSONEncoder().encode(model), encoding: .utf8)!) | |
//{"age":123,"first_name":"Ian","last_name":"Smith"} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
AnyCodingKey
: https://gist.github.com/IanKeen/3d226854c8c59a17e151a0022b71f6bb