Last active
June 3, 2023 21:10
-
-
Save htinlinn/f8bf0abe4e438e33ba3127c8d79adf5f to your computer and use it in GitHub Desktop.
Decode JSON at root level based on a key
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
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
Thank you SO MUCH!!! for this solution...
I have searched all over the internet for the last day and night reading multiple forums, stack overflows web pages
And this is the best and most easiest solution to comprehend and implement
Whoever did this, you must be a very impressive coder... you have literally made my week!! All respect to the person who wrote this