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) |
Lee:
Thanks you very much! With this I can finally handle a JSON file which contains the following:
let jsonString = """
{
"events": {
"141": {
"name": "Ultrabar Fridays",
"event_subtitle": "Each and every Friday party the night away at the hottest party in the city ",
"learnmore_link": "https://www.eventbrite.com/e/ultrabar-fridays-tickets-53478213777",
"image_url": "https://poshnights.com/wp-content/uploads/2021/12/141_image.jpg"
},
"393": {
"name": "Adult Paint and Play DC",
"event_subtitle": "Come out and enjoy a wonderful evening of fun, paint, and cocktails",
"learnmore_link": "https://www.eventbrite.com/e/adult-paint-and-play-dc-tickets-168964053031",
"image_url": "https://poshnights.com/wp-content/uploads/2021/12/393_image.jpg"
}
}
}
“""
Your solution worked perfectly. Here is the result:
▿ __lldb_expr_160.DecodedArray
▿ array: 2 elements
▿ __lldb_expr_160.Event
- name: "Adult Paint and Play DC"
- event_subtitle: "Come out and enjoy a wonderful evening of fun, paint, and cocktails"
- learnmore_link: "https://www.eventbrite.com/e/adult-paint-and-play-dc-tickets-168964053031"
- image_url: "https://poshnights.com/wp-content/uploads/2021/12/393_image.jpg"
▿ __lldb_expr_160.Event
- name: "Ultrabar Fridays"
- event_subtitle: "Each and every Friday party the night away at the hottest party in the city "
- learnmore_link: "https://www.eventbrite.com/e/ultrabar-fridays-tickets-53478213777"
- image_url: "https://poshnights.com/wp-content/uploads/2021/12/141_image.jpg”
Again, many thanks. If you have a Patreon account, let me know and I will make a contribution.
Charlie
… On Apr 17, 2022, at 5:53 AM, Lee Kah Seng ***@***.***> wrote:
@LeeKahSeng commented on this gist.
let jsonString = """ { "students': { "S001": { "firstName": "Tony", "lastName": "Stark" }, "S002": { "firstName": "Peter", "lastName": "Parker" }, "S003": { "firstName": "Bruce", "lastName": "Wayne" } } }
any idea how to handle this structure??????
It is fairly straightforward, all you need to do is to create another data type to hold the student array. Here's how:
struct MyData: Decodable {
let students: DecodedArray<Student>
}
let jsonData = Data(jsonString.utf8)
let decodedResult = try! JSONDecoder().decode(MyData.self, from: jsonData)
print(decodedResult.students)
—
Reply to this email directly, view it on GitHub <https://gist.github.com/ceb374a8f297e0b5982f926f6b43ea12#gistcomment-4135482>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AB2C6PFYH6PWVF33K5VQ5C3VFPNRBANCNFSM5TO42KGQ>.
You are receiving this because you commented.
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] }
}
{
"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
Assuming this is the JSON you trying to decode:
All you need to do is to create a new
Book
struct and update theStudent
struct accordingly: