Hey Folks,
It's been a while since I did some blogging, so I thought I would break the hiatus with an article!
In this post, we will see how we can parse a JSON object which we receive. Typically, our Swift application would be talking to a service, which would return JSON data as the payload on an HTTPResponse, something like this:
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json Date: Mon, 04 May 2020 18:51:34 GMT Connection: keep-alive Content-Length: 65 …. { "emp_id": 20, "join_date": "2020-03-01T22:10:55.200Z", "given_name": "Vipul", "family_name": "Lal" }Typically, we would need to parse the JSON data in the payload of the HTTP response using Swift. Fortunately, this is not very difficult using the Apple provided JSONDecoder class to parse the fields.
In the example below, we have a simple Person class, which we will be parsing from a JSON string.
The Person class has 4 variables:
- EmployeeID which is an integer value
- GivenName which is the Given Name
- Family Name
- Join Date which is a date.
class Person: Codeable
Simply marking the class as Codeable will mean that it can be serialized and de-serialized by Swift. Note that Codeable is an overkill here because we will not be serializing the Person class in this example and we could have simply marked the class as Decodable.
When we make a class as Decodable, Swift library will be able to read in all the member fields. However, we expect that the "over-the-wire" fields of the HTTP Response to be different to the internal names of the Person object. For this, we need to have an enum extending the CodingKey protocol. The items of the enum will give the fields to expect in the JSON object. This gives us added flexibility and the on-the-wire format may be different to the internal field names.
enum OverTheWire : CodingKey { case emp_id case given_name case family_name case join_date }With the above enum, we would expect the field names to be "empty_id", "given_name" etc.
Finally, we declare an initializer which takes a Decoder as a parameter. We extract the fields from the decoder and construct the object from the over-the-wire JSON received.
Here is the entire code. You can create an empty "console" app using Xcode and paste the below code in a file.
// // main.swift // SerilazableTests // // Created by Vipul Lal on 13/3/20. // Copyright © 2020 Far East Software. All rights reserved. // import Foundation class Person : Codable{ let mEmployeeId: Int?; var mGivenName: String?; var mFamilyName: String? var mJoinDate: Date?; // In order to have a different over-the-wire // format, we need to define an enul which implements // the CodingKey protocol enum OverTheWire : CodingKey { case emp_id case given_name case family_name case join_date } // Default init function... init(){ self.mEmployeeId = -1; // Mark instance as not-persisted. } // The seralize/deserailis required init( from decoder: Decoder ) throws { // required because our class is not a final class. do{ let values = try decoder.container( keyedBy: OverTheWire.self ); self.mEmployeeId = try values.decode( Int.self, forKey: .emp_id ); self.mGivenName = try values.decode( String.self, forKey: .given_name ); self.mFamilyName = try values.decode( String.self, forKey: .family_name ); let dateStr = try values.decode( String.self, forKey: .join_date ); let df = DateFormatter(); df.dateFormat = "yyyy-MM-dd HH:mm:ss VV"; self.mJoinDate = df.date( from: dateStr ); } catch { print(" [\(error)] exception while extracting values from the wire format"); throw error; } } } let wireData = """ { "emp_id": 20, "join_date": "2020-03-01T22:10:55.200Z", "given_name": "Vipul", "family_name": "Lal" } """ let mockData:Data? = wireData.data(using: .unicode ); if let data = mockData { do{ let jd = JSONDecoder() jd.dateDecodingStrategy = .iso8601; let p = try JSONDecoder().decode( Person.self, from: data ); print("Yaay! We have a person called \(p.mGivenName) \(p.mFamilyName) ") } catch{ print("\(error) while decoding person record.") } }