-
-
Save LeeKahSeng/ceb374a8f297e0b5982f926f6b43ea12 to your computer and use it in GitHub Desktop.
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) |
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)
Does this method work if the Student Type also contains an nested element? .. I've been trying to decode a JSON response where this is the case using your excellent tutorial, and as soon as I try to decode a nested element it fails to decode.
for example.. I might have a json response of different Schools
, containing Classrooms
, Teachers
and where the Student
name is the identifier, and each Student
has a nested list of Books
they carry..
your method works up to the point i try to decode the nested Books.. (although I might well be making an error somewhere)
Does this method work if the Student Type also contains an nested element? .. I've been trying to decode a JSON response where this is the case using your excellent tutorial, and as soon as I try to decode a nested element it fails to decode.
for example.. I might have a json response of different
Schools
, containingClassrooms
,Teachers
and where theStudent
name is the identifier, and eachStudent
has a nested list ofBooks
they carry..your method works up to the point i try to decode the nested Books.. (although I might well be making an error somewhere)
Assuming this is the JSON you trying to decode:
{
"S001": {
"firstName": "Tony",
"lastName": "Stark",
"books": [
{
"name": "book1"
},
{
"name": "book2"
},
{
"name": "book3"
}
]
},
"S002": {
"firstName": "Peter",
"lastName": "Parker",
"books": []
},
"S003": {
"firstName": "Bruce",
"lastName": "Wayne",
"books": []
}
}
All you need to do is to create a new Book
struct and update the Student
struct accordingly:
struct Book: Decodable {
let name: String
}
struct Student: Decodable {
let firstName: String
let lastName: String
let studentId: String
let books: [Book]
enum CodingKeys: CodingKey {
case firstName
case lastName
case books
}
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
// Decode books
books = try container.decode([Book].self, forKey: CodingKeys.books)
}
}
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!
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??????