Created
January 24, 2019 14:28
-
-
Save AliSoftware/70d5d83f47711598bfefd02f7b3ee555 to your computer and use it in GitHub Desktop.
Techniques to decode Decodable types that are nested under intermediate keys in a JSON
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
import Foundation | |
let json = """ | |
{ | |
"data": { | |
"items": [ | |
{ "name": "Alice" }, | |
{ "name": "Bob" }, | |
{ "name": "Claudia" } | |
] | |
} | |
} | |
""".data(using: .utf8)! | |
struct Item: Decodable, CustomStringConvertible { | |
let name: String | |
var description: String { return "<Item \(name)>" } | |
} | |
//: Solution 1: Create intermediate types just for decoding | |
struct DataWrapper: Decodable { let data: ItemsWrapper } | |
struct ItemsWrapper: Decodable { let items: [Item] } | |
let items = try JSONDecoder().decode(DataWrapper.self, from: json).data.items | |
print(items) | |
//: Solution 2: More generic | |
extension JSONDecoder { | |
private struct Wrapper: Decodable { | |
let container: KeyedDecodingContainer<AnyCodingKey> | |
struct AnyCodingKey: CodingKey { | |
let stringValue: String | |
let intValue: Int? | |
init?(stringValue: String) { | |
self.stringValue = stringValue | |
self.intValue = Int(stringValue) | |
} | |
init?(intValue: Int) { | |
self.stringValue = String(intValue) | |
self.intValue = intValue | |
} | |
} | |
init(from decoder: Decoder) throws { | |
self.container = try decoder.container(keyedBy: AnyCodingKey.self) | |
} | |
func nestedContainer<S: Sequence>(under keys: S) throws -> KeyedDecodingContainer<AnyCodingKey> where S.Element == String { | |
return try keys.reduce(self.container) { d, k in | |
return try d.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue: k)!) | |
} | |
} | |
} | |
func decode<T: Decodable>(_ type: T.Type, from data: Data, nestedIn keys: String...) throws -> T { | |
return try self.decode(Wrapper.self, from: data) | |
.nestedContainer(under: keys.dropLast()) | |
.decode(T.self, forKey: Wrapper.AnyCodingKey(stringValue: keys.last!)!) | |
} | |
} | |
let items2 = try JSONDecoder().decode([Item].self, from: json, nestedIn: "data", "items") | |
print(items2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment