Skip to content

Instantly share code, notes, and snippets.

@haikusw
Forked from htinlinn/DecodableRoot.swift
Created September 17, 2020 05:39
Show Gist options
  • Save haikusw/0d729dee23901f5e75905434e70581a6 to your computer and use it in GitHub Desktop.
Save haikusw/0d729dee23901f5e75905434e70581a6 to your computer and use it in GitHub Desktop.
Decode JSON at root level based on a key
extension JSONDecoder {
func decode<T: Decodable>(_ type: T.Type, from data: Data, keyedBy key: String?) throws -> T {
if let key = key {
// Pass the top level key to the decoder.
userInfo[.jsonDecoderRootKeyName] = key
let root = try decode(DecodableRoot<T>.self, from: data)
return root.value
} else {
return try decode(type, from: data)
}
}
}
extension CodingUserInfoKey {
static let jsonDecoderRootKeyName = CodingUserInfoKey(rawValue: "rootKeyName")!
}
struct DecodableRoot<T>: Decodable where T: Decodable {
private struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.intValue = intValue
stringValue = "\(intValue)"
}
static func key(named name: String) -> CodingKeys? {
return CodingKeys(stringValue: name)
}
}
let value: T
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard
let keyName = decoder.userInfo[.jsonDecoderRootKeyName] as? String,
let key = CodingKeys.key(named: keyName) else {
throw DecodingError.valueNotFound(
T.self,
DecodingError.Context(codingPath: [], debugDescription: "Value not found at root level.")
)
}
value = try container.decode(T.self, forKey: key)
}
}
// Usage
import Foundation
// Slack-style API responses with error information at the top-level.
// https://api.slack.com/web#responses
let data = """
{
"ok": true,
"warning": "something_problematic",
"user": {
"name": "Htin Linn",
"userName": "Litt"
}
}
""".data(using: .utf8)!
struct User: Decodable {
let name: String
let userName: String
}
let jsonDecoder = JSONDecoder()
do {
// This doesn't work because user object is not at the top level.
// let _ = try jsonDecoder.decode(User.self, from: data)
// This doesn't work because `Any` is not `Decodable`.
// let _ = try jsonDecoder.decode([String: Any].self, from: data)
// This doesn't work because the decoder will try to decode every single key at the top-level as a `User` type.
// let _ = try jsonDecoder.decode([String: User].self, from: data)
// But, this works!
let user = try jsonDecoder.decode(User.self, from: data, keyedBy: "user")
print(user)
} catch {
print(error)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment