import Foundation //: # NestedKey /// /// Use this to annotate the properties that require a depth traversal during decoding. /// The corresponding `CodingKey` for this property must be a `NestableCodingKey` @propertyWrapper struct NestedKey: Decodable { var wrappedValue: T struct AnyCodingKey: CodingKey { let stringValue: String let intValue: Int? init(stringValue: String) { self.stringValue = stringValue self.intValue = nil } init?(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } } init(from decoder: Decoder) throws { let key = decoder.codingPath.last! guard let nestedKey = key as? NestableCodingKey else { throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Key \(key) is not a NestableCodingKey")) } let nextKeys = nestedKey.path.dropFirst() // key descent let container = try decoder.container(keyedBy: AnyCodingKey.self) let lastLeaf = try nextKeys.indices.dropLast().reduce(container) { (nestedContainer, keyIdx) in do { return try nestedContainer.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue: nextKeys[keyIdx])) } catch DecodingError.keyNotFound(let key, let ctx) { try NestedKey.keyNotFound(key: key, ctx: ctx, container: container, nextKeys: nextKeys[..( key: CodingKey, ctx: DecodingError.Context, container: KeyedDecodingContainer, nextKeys: C) throws -> Never where C.Element == String { throw DecodingError.keyNotFound(key, DecodingError.Context( codingPath: container.codingPath + nextKeys.map(AnyCodingKey.init(stringValue:)), debugDescription: "NestedKey: No value associated with key \"\(key.stringValue)\"", underlyingError: ctx.underlyingError )) } } //: # NestableCodingKey /// Use this instead of `CodingKey` to annotate your `enum CodingKeys: String, NestableCodingKey`. /// Use a `/` to separate the components of the path to nested keys protocol NestableCodingKey: CodingKey { var path: [String] { get } } extension NestableCodingKey where Self: RawRepresentable, Self.RawValue == String { init?(stringValue: String) { self.init(rawValue: stringValue) } var stringValue: String { path.first! } init?(intValue: Int) { fatalError() } var intValue: Int? { nil } var path: [String] { self.rawValue.components(separatedBy: "/") } }