-
-
Save maximkrouk/1f42b914879aa2d24af471dfc52c78cb to your computer and use it in GitHub Desktop.
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
// Based on https://ilya.puchka.me/decoding-nested-values-with-property-wrappers/ | |
import Foundation | |
public struct CodingProxy: Codable, Equatable { | |
init() {} | |
public init(from decoder: Decoder) {} | |
public func encode(to encoder: Encoder) throws {} | |
public static func ==(lhs: Self, rhs: Self) -> Bool { true } | |
} | |
public protocol _NestedDecodable: Decodable { | |
associatedtype Value: Decodable | |
associatedtype NestedKeys: CodingKey & CaseIterable | |
init(wrappedValue: Value) | |
} | |
@propertyWrapper | |
public struct NestedDecodable<NestedKeys, Value>: _NestedDecodable | |
where Value: Decodable, NestedKeys: CodingKey & CaseIterable { | |
public var wrappedValue: Value | |
public init(wrappedValue: Value) { | |
self.wrappedValue = wrappedValue | |
} | |
} | |
public protocol _NestedEncodable: Encodable { | |
associatedtype Value: Encodable | |
associatedtype NestedKeys: CodingKey & CaseIterable | |
var wrappedValue: Value { get } | |
} | |
@propertyWrapper | |
public struct NestedEncodable<NestedKeys, Value>: _NestedEncodable | |
where Value: Encodable, NestedKeys: CodingKey & CaseIterable { | |
public var wrappedValue: Value | |
public init(wrappedValue: Value) { | |
self.wrappedValue = wrappedValue | |
} | |
} | |
@propertyWrapper | |
public struct NestedCodable<NestedKeys, Value>: _NestedDecodable, _NestedEncodable | |
where Value: Codable, NestedKeys: CodingKey & CaseIterable { | |
public var wrappedValue: Value | |
public init(wrappedValue: Value) { | |
self.wrappedValue = wrappedValue | |
} | |
} | |
extension NestedDecodable: Equatable where Value: Equatable {} | |
extension NestedEncodable: Equatable where Value: Equatable {} | |
extension NestedCodable: Equatable where Value: Equatable {} | |
extension KeyedDecodingContainer { | |
public func decode(_: CodingProxy.Type, forKey key: Key) throws -> CodingProxy { CodingProxy() } | |
public func decode<T: _NestedDecodable>(_: T.Type, forKey key: Key) throws -> T { | |
guard T.NestedKeys.allCases.isEmpty == false else { | |
throw DecodingError.valueNotFound(T.NestedKeys.self, DecodingError.Context(codingPath: codingPath, debugDescription: "No keys defined in \(K.self)")) | |
} | |
let wrappedValue = try containerForNestedKey() | |
.decode(T.Value.self, forKey: T.NestedKeys.lastCase!) | |
return T(wrappedValue: wrappedValue) | |
} | |
public func decode<T: _NestedDecodable, Value>(_: T.Type, forKey key: Key) | |
throws -> T where T.Value == Value? { | |
guard T.NestedKeys.allCases.isEmpty == false else { | |
throw DecodingError.valueNotFound(T.NestedKeys.self, DecodingError.Context(codingPath: codingPath, debugDescription: "No keys defined in \(K.self)")) | |
} | |
let wrappedValue = try containerForNestedKey() | |
.decodeIfPresent(Value.self, forKey: T.NestedKeys.lastCase!) | |
return T(wrappedValue: wrappedValue) | |
} | |
private func containerForNestedKey<K: CodingKey & CaseIterable>() | |
throws -> KeyedDecodingContainer<K> { | |
guard let rootKey = Key(stringValue: K.allCases.first!.stringValue) else { | |
throw DecodingError.valueNotFound(Key.self, DecodingError.Context(codingPath: codingPath, debugDescription: "No root key in \(Key.self) with string value `\(K.allCases.first!.stringValue)`")) | |
} | |
var container = try self.nestedContainer(keyedBy: K.self, forKey: rootKey) | |
if K.allCases.count > 1 { | |
try K.allCases.dropFirst().dropLast().forEach { key in | |
container = try container.nestedContainer(keyedBy: K.self, forKey: key) | |
} | |
} | |
return container | |
} | |
} | |
extension KeyedEncodingContainer { | |
public mutating func encode(_ value: CodingProxy, forKey key: Key) throws {} | |
public mutating func encode<T: _NestedEncodable>(_ value: T, forKey key: Key) throws { | |
guard T.NestedKeys.allCases.isEmpty == false else { | |
throw DecodingError.valueNotFound(T.NestedKeys.self, DecodingError.Context(codingPath: codingPath, debugDescription: "No keys defined in \(K.self)")) | |
} | |
var container: KeyedEncodingContainer<T.NestedKeys> = try containerForNestedKey() | |
try container.encode(value.wrappedValue, forKey: T.NestedKeys.lastCase!) | |
} | |
public mutating func encode<T: _NestedEncodable, Value>(_ value: T, forKey key: Key) | |
throws where T.Value == Value? { | |
guard T.NestedKeys.allCases.isEmpty == false else { | |
throw DecodingError.valueNotFound(T.NestedKeys.self, DecodingError.Context(codingPath: codingPath, debugDescription: "No keys defined in \(K.self)")) | |
} | |
var container: KeyedEncodingContainer<T.NestedKeys> = try containerForNestedKey() | |
try container.encodeIfPresent(value.wrappedValue, forKey: T.NestedKeys.lastCase!) | |
} | |
private mutating func containerForNestedKey<K: CodingKey & CaseIterable>() | |
throws -> KeyedEncodingContainer<K> { | |
guard let rootKey = Key(stringValue: K.allCases.first!.stringValue) else { | |
throw DecodingError.valueNotFound(Key.self, DecodingError.Context(codingPath: codingPath, debugDescription: "No root key in \(Key.self) with string value `\(K.allCases.first!.stringValue)`")) | |
} | |
var container = self.nestedContainer(keyedBy: K.self, forKey: rootKey) | |
if K.allCases.count > 1 { | |
K.allCases.dropFirst().dropLast().forEach { (key) in | |
container = container.nestedContainer(keyedBy: K.self, forKey: key) | |
} | |
} | |
return container | |
} | |
} | |
extension CodingKey where Self: CaseIterable { | |
static var lastCase: Self? { | |
guard allCases.isEmpty == false else { return nil } | |
let lastIndex = allCases.index(allCases.endIndex, offsetBy: -1) | |
return allCases[lastIndex] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Back to index