Decoding Heterogeneous Array - json

I found a lot of examples concerning the implementation to decode an array of heterogeneous objects, but they don't really fit with my situation.
Here is my JSON :
{
"response": [
{
"label": "Shore",
"marineReports": [
{
"id": 1,
"label": "Label"
},
{
"id": 2,
"label": "Label"
}
]
},
{
"label": "Open Sea",
"marineReports": [
{
"id": 0,
"label": "Label"
},
{
"id": 0,
"label": "Label"
}
]
},
{
"label": "Offshore",
"marineReports": [
{
"id": 3,
"label": "Label"
},
{
"id": 3,
"label": "Label"
},
{
"id": 3,
"label": "Label"
}
]
},
{
"label": "Special Reports",
"specialReports": [
{
"label": "Atlantic-Channel",
"reports": [
{
"id": 12,
"label": "Offshore Atlantic"
},
{
"id": 17,
"label": "Channel"
}
]
}
]
}
]
}
Here is what I implemented at first :
struct ResponseSea: Codable {
let result: [SeaArea]
}
struct SeaArea: Codable {
var label: String
var reports: [MarineReport]
struct MarineReport: Codable {
var id: Int
var label: String
}
}
But then I figured out that the last object in the result array is different from the others.
How can I implement a custom parsing logic for a specific object in an array of same object type ?

Based on your JSON it should be like this:
struct RootObject: Codable {
let response: [Response]
}
struct Response: Codable {
let label: String
let marineReports: [Report]?
let specialReports: [SpecialReport]?
}
struct Report: Codable {
let id: Int
let label: String
}
struct SpecialReport: Codable {
let label: String
let reports: [Report]
}
marineReports and specialReports are optional since they may be absent.

Related

Web API how to parse unicode json

I'm really confused by API of web service
Some blocks of data has a format like that:
"ja": "犯罪\u8005の子孫た\u3061が暮\u3089\u3059スラム\u8857\u3002\u5883界線の\u5411\u3053\u3046の人\u3005か\u3089は「族民」\u3068\u3055\u3052\u3059まれ\u3001差\u5225\u3092受\u3051\u3066\u3044た\u3002孤\u5150\u3060\u3063た少年・ルドは\u3001育\u3066の親\u3067\u3042るレグト\u3068\u5171にスラム\u8857に住み\u3001常人離れ\u3057た身体能力\u3092武\u5668に生計\u3092立\u3066\u3066\u3044た\u3002\u3060が\u3042る日\u3001身に覚\u3048のな\u3044罪\u3092\u7740せ\u3089れ\u3001スラムの人\u3005\u3067\u3055\u3048\u6050れる「\u5948落」\u3078\u3068落\u3068\u3055れ\u3066\u3057ま\u3046\u2026\u2026\u3002"
I realized that Swift do not work with those format. But I really don't know in what step I should convert this
My parse func:
URLSession
.shared
.dataTask(with: request) { data, response, error in
if let data = data {
do {
if let sanitisedData = String(data: data, encoding: .utf8)!.map({ String($0) }).filter({ $0 != "\\" }).joined(separator: "").replacingOccurrences(of: "U(.*?)", with: "\\\\u$1", options: .regularExpression).data(using: .utf8) {
let decoder = JSONDecoder()
let decodedResponse = try decoder.decode(Welcome.self, from: sanitisedData)
DispatchQueue.main.async {
self.data = decodedResponse
}
}
} catch let jsonError as NSError {
print("JSON Decode Failed: \(jsonError.localizedDescription)")
}
return
}
}
.resume()
Output:
JSON Decode Failed: The data couldn’t be read because it isn’t in the correct format.
I tried to convert this data to string and that's the result:
犯罪u8005の子孫たu3061が暮u3089u3059スラムu8857u3002u5883界線のu5411u3053u3046の人u3005かu3089は「族民」u3068u3055u3052u3059まれu3001差u5225u3092受u3051u3066u3044たu3002孤u5150u3060u3063た少年・ルドはu3001育u3066の親u3067u3042るレグトu3068u5171にスラムu8857に住みu3001常人離れu3057た身体能力u3092武u5668に生計u3092立u3066u3066u3044たu3002u3060がu3042る日u3001身に覚u3048のなu3044罪u3092u7740せu3089れu3001スラムの人u3005u3067u3055u3048u6050れる「u5948落」u3078u3068落u3068u3055れu3066u3057まu3046u2026u2026u3002
Example of response:
{
"result": "ok",
"response": "collection",
"data": [
{
"id": "f2e906bb-8329-4f93-af70-b6344f18aa07",
"type": "manga",
"attributes": {
"title": {
"en": "This players think i\u2019m one of them"
},
"altTitles": [
{
"ru": "Э\u0442\u0438 \u0438\u0433\u0440ок\u0438 \u0434\u0443м\u0430ю\u0442, \u0447\u0442о я о\u0434\u0438н \u0438\u0437 н\u0438\u0445"
}
],
"description": {
"en": "I am the Dark Lord. The one who united the demons. One who wields limitless power and might. The one who has had enough of it all.\n\nThese people have attacked our lands again!\n\nFor a hundred years now.... vile players have attacked our hellish lands, mercilessly plundering and killing peaceful demons.\n\nToday I will become human, infiltrate the ranks of humanity and destroy their rotten system from within... while these players think I'm one of them.",
"ru": "Я - \u0422\u0435мны\u0439 по\u0432\u0435л\u0438\u0442\u0435ль. \u0422о\u0442, к\u0442о о\u0431ъ\u0435\u0434\u0438н\u0438л \u0434\u0435моно\u0432. \u0422о\u0442, к\u0442о о\u0431л\u0430\u0434\u0430\u0435\u0442 \u0431\u0435\u0441кон\u0435\u0447но\u0439 \u0432л\u0430\u0441\u0442ью \u0438 \u0441\u0438ло\u0439. \u0422о\u0442, ком\u0443 \u0432\u0441\u0435 э\u0442о н\u0430\u0434о\u0435ло.\n\nЭ\u0442\u0438 лю\u0434\u0438 \u0432 о\u0447\u0435\u0440\u0435\u0434но\u0439 \u0440\u0430\u0437 н\u0430п\u0430л\u0438 н\u0430 н\u0430\u0448\u0438 \u0437\u0435мл\u0438!\n\n\u0412о\u0442 \u0443\u0436\u0435 \u0441о\u0442ню л\u0435\u0442\u2026. М\u0435\u0440\u0437к\u0438\u0435 \u0438\u0433\u0440ок\u0438 \u0430\u0442\u0430к\u0443ю\u0442 н\u0430\u0448\u0438 \u0430\u0434\u0441к\u0438\u0435 \u0437\u0435мл\u0438, \u0431\u0435\u0441по\u0449\u0430\u0434но \u0433\u0440\u0430\u0431я \u0438 \u0443\u0431\u0438\u0432\u0430я м\u0438\u0440ны\u0445 \u0434\u0435моно\u0432.\n\n\u0412н\u0435\u0441\u0438\u0442\u0435 Э\u0422О!\n\n\u0421\u0435\u0433о\u0434ня я \u0441\u0442\u0430н\u0443 \u0447\u0435ло\u0432\u0435ком, п\u0440о\u0431\u0435\u0440\u0443\u0441ь \u0432 \u0440я\u0434ы \u0447\u0435ло\u0432\u0435\u0447\u0435\u0441\u0442\u0432\u0430 \u0438 \u0443н\u0438\u0447\u0442о\u0436\u0443 \u0438\u0445 п\u0440о\u0433н\u0438\u0432\u0448\u0443ю \u0441\u0438\u0441\u0442\u0435м\u0443 \u0438\u0437н\u0443\u0442\u0440\u0438... пок\u0430 э\u0442\u0438 \u0438\u0433\u0440ок\u0438 \u0434\u0443м\u0430ю\u0442, \u0447\u0442о я о\u0434\u0438н \u0438\u0437 н\u0438\u0445."
},
"isLocked": false,
"links": {
"raw": "https://remanga.org/manga/this-players-think-im-one-of-them?subpath=about"
},
"originalLanguage": "ru",
"lastVolume": "",
"lastChapter": "",
"publicationDemographic": "seinen",
"status": "ongoing",
"year": 2022,
"contentRating": "suggestive",
"tags": [
{
"id": "36fd93ea-e8b8-445e-b836-358f02b3d33d",
"type": "tag",
"attributes": {
"name": {
"en": "Monsters"
},
"description": [],
"group": "theme",
"version": 1
},
"relationships": []
},
{
"id": "391b0423-d847-456f-aff0-8b0cfc03066b",
"type": "tag",
"attributes": {
"name": {
"en": "Action"
},
"description": [],
"group": "genre",
"version": 1
},
"relationships": []
},
{
"id": "39730448-9a5f-48a2-85b0-a70db87b1233",
"type": "tag",
"attributes": {
"name": {
"en": "Demons"
},
"description": [],
"group": "theme",
"version": 1
},
"relationships": []
},
{
"id": "3e2b8dae-350e-4ab8-a8ce-016e844b9f0d",
"type": "tag",
"attributes": {
"name": {
"en": "Long Strip"
},
"description": [],
"group": "format",
"version": 1
},
"relationships": []
},
{
"id": "87cc87cd-a395-47af-b27a-93258283bbc6",
"type": "tag",
"attributes": {
"name": {
"en": "Adventure"
},
"description": [],
"group": "genre",
"version": 1
},
"relationships": []
},
{
"id": "a1f53773-c69a-4ce5-8cab-fffcd90b1565",
"type": "tag",
"attributes": {
"name": {
"en": "Magic"
},
"description": [],
"group": "theme",
"version": 1
},
"relationships": []
},
{
"id": "cdc58593-87dd-415e-bbc0-2ec27bf404cc",
"type": "tag",
"attributes": {
"name": {
"en": "Fantasy"
},
"description": [],
"group": "genre",
"version": 1
},
"relationships": []
},
{
"id": "e197df38-d0e7-43b5-9b09-2842d0c326dd",
"type": "tag",
"attributes": {
"name": {
"en": "Web Comic"
},
"description": [],
"group": "format",
"version": 1
},
"relationships": []
},
{
"id": "f5ba408b-0e7a-484d-8d49-4e9125ac96de",
"type": "tag",
"attributes": {
"name": {
"en": "Full Color"
},
"description": [],
"group": "format",
"version": 1
},
"relationships": []
}
],
"state": "published",
"chapterNumbersResetOnNewVolume": false,
"createdAt": "2022-01-10T17:15:53+00:00",
"updatedAt": "2022-01-10T17:26:26+00:00",
"version": 4,
"availableTranslatedLanguages": [
"ru"
]
},
"relationships": [
{
"id": "5537d8ed-16ed-4f6f-af75-ad7d7edb2ddc",
"type": "author"
},
{
"id": "36ca9e78-35a9-474d-b4e6-0e4065f0af87",
"type": "artist"
},
{
"id": "93e21cc1-1f61-46ce-8362-90d9d6888f63",
"type": "artist"
},
{
"id": "0e703bf4-1996-432c-8620-3c08f2edb37b",
"type": "cover_art"
}
]
}
],
"limit": 1,
"offset": 0,
"total": 57
}
If your server response is really without the unicode escaped, that is,
"犯罪\u8005の子孫た\u3061 ...", then try escaping the data as shown in this example code,
to get: "犯罪\\u8005の子孫た\\u3061 ..." (note the \\) and decode it like this:
URLSession
.shared
.dataTask(with: request) { data, response, error in
if let data = data {
do {
if let sanitisedData = String(data: data, encoding: .utf8)!
.replacingOccurrences(of: "\\", with: "\\\\") // <-- here
.data(using: .utf8) {
let decoder = JSONDecoder()
let decodedResponse = try decoder.decode(Welcome.self, from: sanitisedData)
DispatchQueue.main.async {
self.data = decodedResponse
}
}
} catch let jsonError as NSError {
print("JSON Decode Failed: \(jsonError.localizedDescription)")
}
return
}
}.resume()
EDIT-1:
you may also need to do this: .replacingOccurrences(of: "\n", with: "\\n")
You don't need to process the data returned at all if the result is simply a text (string to say welcome someone). My solution below is just loading a JSON file returned (as you mentioned above) and then decode the data to Welcome object below
struct Welcome: Codable {
let ja: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
ja = try values.decodeIfPresent(String.self, forKey: .ja) ?? ""
}
}
func readJsonFile(filename: String) -> String {
guard let fileUrl = Bundle.main.url(forResource: filename, withExtension: "json") else { fatalError() }
guard let jsonData = try? String(contentsOf: fileUrl) else {
return ""
}
return jsonData
}
func parseJsonFile() {
let jsonStr = readJsonFile(filename: "data")
let jsonData: Data = Data(jsonStr.utf8)
let decoder = JSONDecoder()
do {
let welcome = try decoder.decode(Welcome.self, from: jsonData)
print("\(welcome.ja)")
} catch {
print(error.localizedDescription)
}
}
this is the result when I print the welcome.ja's value:
犯罪者の子孫たちが暮らすスラム街。境界線の向こうの人々からは「族民」とさげすまれ、差別を受けていた。孤児だった少年・ルドは、育ての親であるレグトと共にスラム街に住み、常人離れした身体能力を武器に生計を立てていた。だがある日、身に覚えのない罪を着せられ、スラムの人々でさえ恐れる「奈落」へと落とされてしまう……。

Flutter: parsing json response that has multiple keys

I'm trying to get the values of these keys from this json response:
{
"pro": {
"groups": [
"1": {
"name": "Base",
"fields": [
{
"id": 3,
"value": {
"raw": "Name",
}
},
{
"id": 4,
"value": {
"raw": "avatar",
}
},
]
},
"2": {
"name": "Base",
"fields": [
{
"id": 6,
"value": {
"raw": "Name",
}
},
{
"id": 7,
"value": {
"raw": "avatar",
}
},
]
}
]
}
}
I could get the values "name": "Base"
json['pro']['groups']["1"]['name'],
But I can't get the values of key "raw".
How can I get the values of key "raw"?
The values of fields are a list, so you will get a list of raw values:
List<String> raw = json['pro']['groups']['1']['fields'].map((v) => v['value']['raw'];
Also, it seems like groups is an array but as an object? then you can do something like this:
List<String> raw = [];
Map<String, dynamic> groups = json['pro']['groups'];
for (var key in groups.keys) {
raw.add(groups[key]['fields'].map((v) => v['value']['raw']);
}
or
List<String> raw = groups.keys.map((key) => groups[key]['fields'].map((v) => v['value']['raw']);
I haven't tested the code, but hopefully, it works as expected :)
first thing first. your json is invalid.
try to paste your json here it will show why is your json is invalid
after you fix the json the new structure json will be looked like this
{
"pro": {
"groups": [
{
"name": "Base",
"fields": [
{
"id": 3,
"value": {
"raw": "Name",
}
},
{
"id": 4,
"value": {
"raw": "avatar",
}
},
]
},
{
"name": "Base",
"fields": [
{
"id": 6,
"value": {
"raw": "Name",
}
},
{
"id": 7,
"value": {
"raw": "avatar",
}
},
]
}
]
}
}
and then in order to grab the value of raw, you have to parse the json first using jsonDecode(), then you can use something like this:
Map<String, dynamic> groupOne = json['pro']['groups'][0];
Map<String, dynamic> groupOneFieldOne = groupOne['fields'][0];
print(groupOneFieldOne['value']['raw']);
but that's just an example. if you want to access them easily you can use .map() like this:
List<Map<String, dynamic>> groups = json['pro']['groups'];
groups.map(
(Map<String, dynamic> group) => (group['fields'] as List<dynamic>).map(
(dynamic field) => field['value']['raw'],
),
);
that's it! if you want to ask anything just put a comment ;)
you can copy and paste on dartpad
List<Map<String, dynamic>> groups = json['pro']['groups'];
print(groups.map(
(Map<String, dynamic> group) => (group['fields'] as List<dynamic>).map(
(dynamic field) => field['value']['raw'],
),
));
}
Map<String, dynamic> json = {
"pro": {
"groups": [
{
"name": "Base",
"fields": [
{
"id": 3,
"value": {
"raw": "Name",
}
},
{
"id": 4,
"value": {
"raw": "avatar",
}
},
]
},
{
"name": "Base",
"fields": [
{
"id": 6,
"value": {
"raw": "Name",
}
},
{
"id": 7,
"value": {
"raw": "avatar",
}
},
]
}
]
}
};

How to decode dynamic nested JSON in Swift

I have been trying to decode JSON from a backend system.
{
"Inst1": [
{
"symbol": "Inst1",
"id": 357200929,
"ltd": 20191220
},
{
"symbol": " Inst1",
"id": 357200932,
"ltd": 20191220
},
{…}
],
"Inst2": [
{
"symbol": "Inst2",
"id": 357201388,
"ltd": 20191220
},
{
"symbol": "Inst2",
"id": 371886725,
"ltd": 20200320
}
]
}
The name as well as the number of top-level nodes (Inst1, Inst2) are variable and unknown. I believe the correct name for such nodes are “Dynamic Coding Keys” and that is why enum cannot be used.
The sub-nodes are in an array and always share the same structure. Their number are unknown and can be different for each top-node (e.g. inst1 may have 10 sub-nodes but Inst2 can have 5).
I’m struggling to find the correct way to decode/encode such reply. Any help would be very much appreciated. Thank you
Following Sh_Khan answer I tried the below code but it doesn't work.
var json = """
{
"Inst1": [
{
"symbol": "Inst1",
"id": 357200929,
"ltd": 20191220
},
{
"symbol": " Inst1",
"id": 357200932,
"ltd": 20191220
}
],
"Inst2": [
{
"symbol": "Inst2",
"id": 357201388,
"ltd": 20191220
},
{
"symbol": "Inst2",
"id": 371886725,
"ltd": 20200320
}
]
}
""".data(using: .utf8)!
struct Inst: Codable {
let symbol: String
let id, ltd: Int
}
let result = try? JSONDecoder().decode([String:[Inst]].self,from: json)
You need
let res = try? JSONDecoder().decode([String:[Inst]],from:data)
struct Inst: Codable {
let symbol: String
let id, ltd: Int
}
Correct json
var json = """
{
"Inst1": [
{
"symbol": "Inst1",
"id": 357200929,
"ltd": 20191220
},
{
"symbol": " Inst1",
"id": 357200932,
"ltd": 20191220
}
],
"Inst2": [
{
"symbol": "Inst2",
"id": 357201388,
"ltd": 20191220
},
{
"symbol": "Inst2",
"id": 371886725,
"ltd": 20200320
}
]
}
""".data(using: .utf8)!
The solution provided by Sh_Khan works.
let res = try? JSONDecoder().decode([String:[Inst]],from:data)
struct Inst: Codable {
let symbol: String
let id, ltd: Int
}
Thanks for your input.

Unmarshal single-item JSON list to single nested struct

I have JSON coming from a graph database and it only returns lists. Here is an example query and response:
{
Course(func: eq(XID, "1")) {
Name
Holes : Hole {
Number
Tee {
Color
Basket #facets(distance) {
Designation
}
}
}
}
}
This query looks up by ID so it can only return one result but the nature of the database backend means it will always return a list like so:
{
"Course": [
{
"Name": "NAD",
"Holes": [
{
"Number": 1,
"Tee": [
{
"Color": "Blue",
"Basket": [
{
"Designation": "A",
"Basket|distance": "70"
}
]
},
{
"Color": "Red",
"Basket": [
{
"Designation": "A",
"Basket|distance": "69"
}
]
}
]
},
{
"Number": 2,
"Tee": [
{
"Color": "Blue",
"Basket": [
{
"Designation": "A",
"Basket|distance": "79"
},
{
"Designation": "B",
"Basket|distance": "89"
}
]
},
{
"Color": "Red",
"Basket": [
{
"Designation": "A",
"Basket|distance": "79"
},
{
"Designation": "B",
"Basket|distance": "95"
}
]
}
]
}
]
}
]
}
I want to unmarshal that single Course to a struct:
type Course struct {
Name string `json:"Name"`
Holes []struct {
Number int `json:"Number"`
Tee []struct {
Color string `json:"Color"`
Basket []struct {
Designation string `json:"Designation"`
BasketDistance string `json:"Basket|distance"`
} `json:"Basket"`
} `json:"Tee"`
} `json:"Holes"`
}
I tried making a temporary struct that is just a list of courses to pull out the correct JSON by index but I get a really generic runtime error that the object can't be unmarshaled to that type.
I believe it would be relatively easy to convert it to a []map[string]interface{} then unmarshal to a Course from there by specifying the JSONRawMessage type but doesn't that suffer a performance penalty? What's the most performant way to solve this problem?

Decoding JSON with Codable issue. keyNotFound error message

I have problem with decoding JSON. I am trying to decode my JSON with
let temp = try JSONDecoder().decode([LastTemperatureResponse].self, from: data).
My Codable structs is following:
struct LastTemperatureResponseElement: Codable {
let measurement: Measurement
}
struct Measurement: Codable {
let ts: String
let sensors: [VportSensor]
}
struct VportSensor: TemperatureSensor, Codable {
var lastUpdate: String!
let address, description: String
let status: String
let temperature: Double
}
Well, if I'm trying to decode my JSON, I am getting error message where it's quite clear
keyNotFound(CodingKeys(stringValue: "status", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "measurement", intValue: nil), CodingKeys(stringValue: "sensors", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"status\", intValue: nil) (\"status\").", underlyingError: nil))
but please take a look on my JSON
[
{
"type": "temperatures",
"ts": "2017-11-08T16:43:59.558Z",
"source": "thermo-king",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2017-11-08T16:43:18.000Z",
"sensors": [
{
"address": "t1",
"description": "LFTest1",
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
},
{
"address": "t2",
"description": "LFTest2",
"setpoints": [
{
"address": "s2",
"name": "LFSTest2"
}
]
},
{
"address": "t3",
"description": "LFTest3",
"setpoints": [
{
"address": "s3",
"name": "LFSTest3"
}
]
},
{
"address": "t4",
"description": "LFTest4"
},
{
"address": "t5",
"description": "LFTest5"
},
{
"address": "t6",
"description": "LFTest6"
}
],
"sensor": {
"address": "t1",
"name": "LFTest1"
},
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:05:38.962Z",
"source": "1-wire",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:05:31.000Z",
"sensors": [
{
"address": "2839A5B104000004",
"description": "1-wire #1",
"status": "ok",
"temperature": 24.8
},
{
"address": "28EFBAB104000061",
"description": "1-wire #3",
"status": "ok",
"temperature": 24.5
},
{
"address": "2845F6B504000034",
"description": "1-wire #2",
"status": "ok",
"temperature": 24.5
}
],
"sensor": {
"address": "2839A5B104000004",
"name": "1-wire #1",
"status": "ok"
},
"temperature": 24.8
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:11:50.030Z",
"source": "vport",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:11:47.000Z",
"sensors": [
{
"address": "1036040010",
"description": "Vport 1-wire",
"status": "high",
"temperature": 26
}
],
"sensor": {
"address": "1036040010",
"name": "Vport 1-wire",
"status": "high"
},
"temperature": 26
}
}
]
So I can guess that is giving error because of first portion of data, but should it be omitted and data generated with the rest?
After tracing your issue, I figured that there is couple of issues, First of all:
You are NOT declaring optionals:
based on the attached json, it seems that there are some of the properties that do not always exist, such as:
status => VportSensor.
temperature => Measurement.
temperature => VportSensor.
temperature => setpoints.
you would need make sure to declare any property that may not received as optional.
Also, The implementation of the Codeable structs:
the implemented structs seem to be not typical to the json response structure, make sure to declare your codable structs to be matched with the received json structure.
Note That:
lastUpdate and description are not used in VportSensor.
Based on my answer, there is no need to TemperatureSensor...
Tip:
When it comes to working with dates (such as ts), you should declare it directly as Date instead of String and then set the convenient dateDecodingStrategy. In your case, it should be a custom one, you could find how to do it in this answer.
Implementation:
Based on the above description, there is the full implementation:
struct Main: Codable {
let type: String
let ts: Date
let source: String
let unit: Unit
let measurement: Measurement
}
struct Unit: Codable {
var number: String
}
struct Measurement: Codable {
let ts: String
let sensors: [VportSensor]
let sensor: VportSensor
let temperature: Double?
}
struct LastTemperatureResponseElement: Codable {
let measurement: Measurement
}
struct VportSensor: Codable {
//let lastUpdate: String!
//let description: String
let address: String
let name: String?
let status: String?
let temperature: Double?
let setpoints: [Setpoint]?
}
struct Setpoint: Codable {
let address: String
let name: String
}
// this part from the mentioned answer for creating custom `dateDecodingStrategy`:
enum DateError: String, Error {
case invalidDate
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
throw DateError.invalidDate
})
Output:
let decoder = JSONDecoder()
do {
let temp = try decoder.decode([Main].self, from: json)
// here we go, `temp` is an array of main object of the json
} catch {
print(error)
}
If you are wondering what is json in the
let temp = try decoder.decode([Main].self, from: json)
I just took the attached json response and add it into Data Object:
let json = """
[
{
"type": "temperatures",
"ts": "2017-11-08T16:43:59.558Z",
"source": "thermo-king",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2017-11-08T16:43:18.000Z",
"sensors": [
{
"address": "t1",
"description": "LFTest1",
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
},
{
"address": "t2",
"description": "LFTest2",
"setpoints": [
{
"address": "s2",
"name": "LFSTest2"
}
]
},
{
"address": "t3",
"description": "LFTest3",
"setpoints": [
{
"address": "s3",
"name": "LFSTest3"
}
]
},
{
"address": "t4",
"description": "LFTest4"
},
{
"address": "t5",
"description": "LFTest5"
},
{
"address": "t6",
"description": "LFTest6"
}
],
"sensor": {
"address": "t1",
"name": "LFTest1"
},
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:05:38.962Z",
"source": "1-wire",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:05:31.000Z",
"sensors": [
{
"address": "2839A5B104000004",
"description": "1-wire #1",
"status": "ok",
"temperature": 24.8
},
{
"address": "28EFBAB104000061",
"description": "1-wire #3",
"status": "ok",
"temperature": 24.5
},
{
"address": "2845F6B504000034",
"description": "1-wire #2",
"status": "ok",
"temperature": 24.5
}
],
"sensor": {
"address": "2839A5B104000004",
"name": "1-wire #1",
"status": "ok"
},
"temperature": 24.8
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:11:50.030Z",
"source": "vport",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:11:47.000Z",
"sensors": [
{
"address": "1036040010",
"description": "Vport 1-wire",
"status": "high",
"temperature": 26
}
],
"sensor": {
"address": "1036040010",
"name": "Vport 1-wire",
"status": "high"
},
"temperature": 26
}
}
]
""".data(using: .utf8)!
You can easily skip the keys those are not getting from server response.
Example JSON response is:
{
"isValid": false,
"pendingAttempts": 2
}
In this json response there is missing "id" field & in our code we have declared it. So we can easily skip it by the following code.
//Code example
struct ResponseModel: Codable {
var id: String? //misng in response
var isValid: Bool?
var token: String?
//initializer
init(id: String?, isValid: Bool?, token: String?) {
self.id = id
self.isValid = isValid
self.token = token
}
//definging the coding keys
enum ResponseModelCodingKeys: String, CodingKey {
//The right hand side keys should be same as of json response keys
case id = "id"
case isValid = "isValid"
case token = "token"
}
//decoding initializer
init(from decoder: Decoder) throws {
var id: String?
var isValid: Bool?
var token: String?
let container = try decoder.container(keyedBy: ResponseModelCodingKeys.self) // defining our (keyed) container
do {
//if found then map
id = try container.decode(String.self, forKey: .id)
}
catch {
//not found then just set the default value
/******** This case will be executed **********/
id = ""
}
do {
//if found then map
isValid = try container.decode(Bool.self, forKey: .isValid)
}
catch {
//not found then just set the default value
isValid = false
}
do {
//if found then map
token = try container.decode(String.self, forKey: .token)
}
catch {
//not found then just set the default value
token = ""
}
//Initializing the model
self.init(id: id, isValid: isValid, token: token)
}
}
This technique is useful when we have common response for multiple API's & each API have some missing keys.