Skip to content

Instantly share code, notes, and snippets.

@LeeKahSeng
Last active June 4, 2023 14:04
Show Gist options
  • Save LeeKahSeng/ceb374a8f297e0b5982f926f6b43ea12 to your computer and use it in GitHub Desktop.
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/)
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)
@cparker101
Copy link

cparker101 commented Oct 11, 2022 via email

@gmcusaro
Copy link

gmcusaro commented Jun 4, 2023

In extension DecodedArray: Collection the correct associated type for a collection's iterator element is Element, not Iterator.Element.

    // Required subscript, based on a dictionary index
    subscript(index: Index) -> Element {
        get { return array[index] }
    }

@phuongddx
Copy link

{
  "EventName": "broadcast-price",
  "EventData": {
    "data": {
      "XRPUSDT": {
        "change24h": 0.27,
        "change7d": 10.89,
        "exchange": "bsc",
        "icon": "",
        "id": "",
        "isDown": false,
        "isUp": true,
        "max24h": 0,
        "min24h": 0,
        "name": "",
        "pair": "",
        "price": 0.5245,
        "symbol": "XRPUSDT",
        "timeStamp": "2023-06-04T07:41:57.29489505Z",
        "volume_24h": 71163040.90439999,
        "volume_24h_origin": 0
      },
      "exchange": "bsc",
      "token": "XRPUSDT"
    },
    "event": "symbol-data"
  }
}

How about for single object sir? @LeeKahSeng Thanks!

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