Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Decoding nested values with property wrapper
import Foundation
public struct Unit: Codable, Equatable {
init() {}
public init(from decoder: Decoder) {}
public func encode(to encoder: Encoder) throws {}
public static func ==(lhs: Self, rhs: Self) -> Bool {
return true
}
}
public protocol _NestedDecodable: Decodable {
associatedtype Value: Decodable
associatedtype NestedKeys: CodingKey & CaseIterable
init(wrappedValue: Value)
}
@propertyWrapper
public struct NestedDecodable<Value: Decodable, NestedKeys: CodingKey & CaseIterable>: _NestedDecodable {
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<Value: Encodable, NestedKeys: CodingKey & CaseIterable>: _NestedEncodable {
public var wrappedValue: Value
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
@propertyWrapper
public struct NestedCodable<Value: Codable, NestedKeys: CodingKey & CaseIterable>: _NestedDecodable, _NestedEncodable {
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(_: Unit.Type, forKey key: Key) throws -> Unit { Unit() }
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: Unit, 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]
}
}
struct Contact: Codable, Equatable {
var id: String
@NestedCodable<String, FirstNameKeys>
var firstname: String
@NestedCodable<String?, LastNameKeys>
var lastname: String?
@NestedCodable<String, AddressKeys>
var address: String
// here just for compiler, does not decode anything
private var user: Unit
enum FirstNameKeys: String, CodingKey, CaseIterable {
case user, details, name, first
}
enum LastNameKeys: String, CodingKey, CaseIterable {
case user, details, name, last
}
enum AddressKeys: String, CodingKey, CaseIterable {
case user, details, address
}
}
let data = """
{
"id": "1",
"user": {
"details": {
"address": "Apple St.",
"name": {
"first": "Swift",
"last": "Language"
}
}
}
}
""".data(using: .utf8)!
do {
let person = try JSONDecoder().decode(Contact.self, from: data)
print(person)
let data1 = try JSONEncoder().encode(person)
print(String(data: data1, encoding: .utf8)!)
} catch {
print(error)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.