Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Last active July 13, 2022 23:57
Show Gist options
  • Save IanKeen/9e77041c386cd5ba0128330d6149c5a4 to your computer and use it in GitHub Desktop.
Save IanKeen/9e77041c386cd5ba0128330d6149c5a4 to your computer and use it in GitHub Desktop.
PropertyWrapper: NestedKey - deal with en/de-coding values within nested data
let json = """
{
"name": {
"first": "Ian",
"last": "Keen"
},
"age": 42,
"settings": {
"permissions": {
"admin": true
}
}
}
"""
struct User: Codable {
private enum CodingKeys: String, NestedCodingKey {
case firstName = "name.first"
case lastName = "name.last"
case age
case isAdmin = "setting.permissions.admin"
}
@NestedKey var firstName: String
@NestedKey var lastName: String
var age: Int
@NestedKey var isAdmin: Bool
}
public protocol NestedCodingKey: CodingKey {
var nestedKeys: [String] { get }
}
@propertyWrapper
public struct NestedKey<T: Codable>: Codable {
public var wrappedValue: T
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
public init(from decoder: Decoder) throws {
let codingKey = decoder.codingPath.last!
guard let key = codingKey as? NestedCodingKey else {
throw DecodingError.keyNotFound(codingKey, .init(codingPath: decoder.codingPath, debugDescription: "CodingKeys must conform to NestedCodingKey"))
}
let last = key.nestedKeys.last!
let container = try key.nestedKeys.dropFirst().dropLast().reduce(decoder.container(keyedBy: AnyCodingKey.self)) { container, key in
return try container.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(key))
}
self.wrappedValue = try container.decode(T.self, forKey: AnyCodingKey(last))
}
public func encode(to encoder: Encoder) throws {
let codingKey = encoder.codingPath.last!
guard let key = codingKey as? NestedCodingKey else {
throw EncodingError.invalidValue(codingKey, .init(codingPath: encoder.codingPath, debugDescription: "CodingKeys must conform to NestedCodingKey"))
}
let last = key.nestedKeys.last!
var container = encoder.container(keyedBy: AnyCodingKey.self)
for key in key.nestedKeys.dropFirst().dropLast() {
container = container.nestedContainer(keyedBy: AnyCodingKey.self, forKey: .init(key))
}
try container.encode(wrappedValue, forKey: AnyCodingKey(last))
}
}
extension NestedKey: Equatable where T: Equatable { }
extension KeyedDecodingContainer {
public func decode<T>(_ type: NestedKey<T?>.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> NestedKey<T?> {
return NestedKey<T?>(wrappedValue: try? decode(NestedKey<T>.self, forKey: key).wrappedValue)
}
}
extension NestedCodingKey where Self: RawRepresentable, RawValue == String {
public init?(stringValue: String) { self.init(rawValue: stringValue) }
public init?(intValue: Int) { fatalError() }
public var intValue: Int? { nil }
public var stringValue: String { nestedKeys.first! }
public var nestedKeys: [String] { rawValue.components(separatedBy: ".") }
}
@AliSoftware
Copy link

👋

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