Last active
June 4, 2023 14:04
-
-
Save LeeKahSeng/ceb374a8f297e0b5982f926f6b43ea12 to your computer and use it in GitHub Desktop.
Decode and Flatten JSON with Dynamic Keys Using Swift Decodable (https://swiftsenpai.com/swift/decode-dynamic-keys-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 jsonString = """ | |
{ | |
"S001": { | |
"firstName": "Tony", | |
"lastName": "Stark" | |
}, | |
"S002": { | |
"firstName": "Peter", | |
"lastName": "Parker" | |
}, | |
"S003": { | |
"firstName": "Bruce", | |
"lastName": "Wayne" | |
} | |
} | |
""" | |
struct Student: Decodable { | |
let firstName: String | |
let lastName: String | |
let studentId: String | |
enum CodingKeys: CodingKey { | |
case firstName | |
case lastName | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: CodingKeys.self) | |
// Decode firstName & lastName | |
firstName = try container.decode(String.self, forKey: CodingKeys.firstName) | |
lastName = try container.decode(String.self, forKey: CodingKeys.lastName) | |
// Extract studentId from coding path | |
studentId = container.codingPath.first!.stringValue | |
} | |
} | |
struct DecodedArray<T: Decodable>: Decodable { | |
typealias DecodedArrayType = [T] | |
private var array: DecodedArrayType | |
// Define DynamicCodingKeys type needed for creating decoding container from JSONDecoder | |
private struct DynamicCodingKeys: CodingKey { | |
// Use for string-keyed dictionary | |
var stringValue: String | |
init?(stringValue: String) { | |
self.stringValue = stringValue | |
} | |
// Use for integer-keyed dictionary | |
var intValue: Int? | |
init?(intValue: Int) { | |
// We are not using this, thus just return nil | |
return nil | |
} | |
} | |
init(from decoder: Decoder) throws { | |
// Create decoding container using DynamicCodingKeys | |
// The container will contain all the JSON first level key | |
let container = try decoder.container(keyedBy: DynamicCodingKeys.self) | |
var tempArray = DecodedArrayType() | |
// Loop through each keys in container | |
for key in container.allKeys { | |
// Decode T using key & keep decoded T object in tempArray | |
let decodedObject = try container.decode(T.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!) | |
tempArray.append(decodedObject) | |
} | |
// Finish decoding all T objects. Thus assign tempArray to array. | |
array = tempArray | |
} | |
} | |
// Transform DecodedArray into custom collection | |
extension DecodedArray: Collection { | |
// Required nested types, that tell Swift what our collection contains | |
typealias Index = DecodedArrayType.Index | |
typealias Element = DecodedArrayType.Element | |
// The upper and lower bounds of the collection, used in iterations | |
var startIndex: Index { return array.startIndex } | |
var endIndex: Index { return array.endIndex } | |
// Required subscript, based on a dictionary index | |
subscript(index: Index) -> Iterator.Element { | |
get { return array[index] } | |
} | |
// Method that returns the next index when iterating | |
func index(after i: Index) -> Index { | |
return array.index(after: i) | |
} | |
} | |
let jsonData = Data(jsonString.utf8) | |
// Decode JSON into [Student] | |
let decodedResult = try! JSONDecoder().decode(DecodedArray<Student>.self, from: jsonData) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How about for single object sir? @LeeKahSeng Thanks!