Skip to content

Instantly share code, notes, and snippets.

@htinlinn
Last active June 3, 2023 21:10
Show Gist options
  • Save htinlinn/f8bf0abe4e438e33ba3127c8d79adf5f to your computer and use it in GitHub Desktop.
Save htinlinn/f8bf0abe4e438e33ba3127c8d79adf5f 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)
}
@jamesieboye
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment