I'm learning Swift and want to make an app that will show the GPS coordinates of buses on a map. The bus lat and lon come from a JSON (excerpt below):
{
"result":[
{
"Lat":52.276408,
"Lon":21.167618,
"Time":"2018-08-24 11:50:05",
"Lines":"225",
"Brigade":"4"
},
{
"Lat":52.222656,
"Lon":21.102633,
"Time":"2018-08-24 11:51:03",
"Lines":"225",
"Brigade":"2"
},
{
"Lat":52.2100185,
"Lon":21.2054211,
"Time":"2018-08-24 11:51:08",
"Lines":"119",
"Brigade":"2"
},
{
"Lat":52.1676735,
"Lon":21.2222606,
"Time":"2018-08-24 11:51:07",
"Lines":"213",
"Brigade":"3"
}
]
}
I was thinking of creating a Bus class
class Bus {
var latitude : Double = 1.11
var longitude : Double = 2.22
var lines : Int = 0
init (lat: Double, lon: Double, line: Int) {
latitude = lat
longitude = lon
lines = line
}
}
But I'm trying to figure out how to create a collection of these bus objects from the JSON, which (in full) contains around 1000 objects (amount varies throughout the day).
Could someone point me in the right direction? I don't need the fully-coded solution, just some pointers on how should I approach this.
I will most likely be using the SwiftyJSON CocoaPod for JSON parsing, together with Alamofire for getting it.
Thank you!
you can use Below Codable class as Larme said in the comments.
import Foundation
class MyModelClass: Codable {
let result: [Result]
init(result: [Result]) {
self.result = result
}
}
class Result: Codable {
let lat, lon: Double
let time, lines, brigade: String
enum CodingKeys: String, CodingKey {
case lat = "Lat"
case lon = "Lon"
case time = "Time"
case lines = "Lines"
case brigade = "Brigade"
}
init(lat: Double, lon: Double, time: String, lines: String, brigade: String) {
self.lat = lat
self.lon = lon
self.time = time
self.lines = lines
self.brigade = brigade
}
}
If you wanted to use ObjectMapper then you can use below class
import Foundation
import ObjectMapper
class MyModelClass: Mappable {
var result: [Result]?
required init?(map: Map){
}
func mapping(map: Map) {
result <- map["result"]
}
}
class Result: Mappable {
var lat: NSNumber?
var lon: NSNumber?
var time: String?
var lines: String?
var brigade: String?
required init?(map: Map){
}
func mapping(map: Map) {
lat <- map["Lat"]
lon <- map["Lon"]
time <- map["Time"]
lines <- map["Lines"]
brigade <- map["Brigade"]
}
}
write List model like this
class BusListModel {
var list = [Bus]()
var longitude : Double = 2.22
var lines : Int = 0
init (With dict:[String:Any]) {
if let result = dict["result"] as? [[String:Any]]{
for busDetail in result{
let model = Bus(lat: **valueHere**, lon: **valueHere**, line: **valueHere**)
list.append(model)
}
}
}
}
Thank you all for the answers, especially #dahiya_boy
I customised the code posted by #dahiya_boy and ended up with exactly what I need.
Below is the code. I created a jsonString for example purposes.
import Foundation
class MyModelClass: Codable {
let busArray: [Bus]
enum CodingKeys: String, CodingKey {
case busArray = "result"
}
init(busArray: [Bus]) {
self.busArray = busArray
}
}
class Bus: Codable {
let lat, lon: Double
let time, lines, brigade: String
enum CodingKeys: String, CodingKey {
case lat = "Lat"
case lon = "Lon"
case time = "Time"
case lines = "Lines"
case brigade = "Brigade"
}
init(lat: Double, lon: Double, time: String, lines: String, brigade: String) {
self.lat = lat
self.lon = lon
self.time = time
self.lines = lines
self.brigade = brigade
}
}
var jsonString = """
{
"result":[
{
"Lat":52.276408,
"Lon":21.167618,
"Time":"2018-08-24 11:50:05",
"Lines":"225",
"Brigade":"4"
},
{
"Lat":52.222656,
"Lon":21.102633,
"Time":"2018-08-24 11:51:03",
"Lines":"225",
"Brigade":"2"
},
{
"Lat":52.2100185,
"Lon":21.2054211,
"Time":"2018-08-24 11:51:08",
"Lines":"119",
"Brigade":"2"
},
{
"Lat":52.1676735,
"Lon":21.2222606,
"Time":"2018-08-24 11:51:07",
"Lines":"213",
"Brigade":"3"
}
]
}
"""
if let jsonData = jsonString.data(using: .utf8) {
let decodedJSON = try! JSONDecoder().decode(MyModelClass.self, from: jsonData)
print("Latitude: \(decodedJSON.busArray[0].lat), Longitude: \(decodedJSON.busArray[0].lon), Line: \(decodedJSON.busArray[0].lines)")
}
This prints the following console output:
Latitude: 52.276408, Longitude: 21.167618, Line: 225
Related
I am trying to handle a JSON with Codable but there's a parsing error when I decode it with JSONDecoder().decode.
{
"data": [
{
"id": "90",
"symbol": "BTC",
"name": "Bitcoin",
"nameid": "bitcoin",
"rank": 1,
"price_usd": "50513.75",
"percent_change_24h": "3.03",
"percent_change_1h": "-0.50",
"percent_change_7d": "-9.91",
"price_btc": "1.00",
"market_cap_usd": "942710364520.73",
"volume24": 70745042591.75044,
"volume24a": 107034995571.4168,
"csupply": "18662452.00",
"tsupply": "18662452",
"msupply": "21000000"
},
{
"id": "80",
"symbol": "ETH",
"name": "Ethereum",
"nameid": "ethereum",
"rank": 2,
"price_usd": "4052.44",
"percent_change_24h": "10.17",
"percent_change_1h": "-0.78",
"percent_change_7d": "17.75",
"price_btc": "0.084812",
"market_cap_usd": "466734637594.73",
"volume24": 53134000887.50444,
"volume24a": 87082811090.79503,
"csupply": "115173595.00",
"tsupply": "115173595",
"msupply": ""
}
],
"info": {
"coins_num": 5949,
"time": 1621022046
}
}
The json is like above and I coded all my models like below.
class CoinModel: Codable {
var data: [Coin]?
var info: CoinInfo?
enum CodingKeys: String, CodingKey {
case data
case info
}
}
class CoinInfo: Codable {
var coinsNum: Int?
var time: TimeInterval?
enum CodingKeys: String, CodingKey {
case coinsNum = "coins_num"
case time = "time"
}
}
class Coin: Codable{
var csupply: String?
var id: String?
var marketCapUsd: String?
var msupply: Int?
var name: String?
var nameid: String?
var percentChange1h: String?
var percentChange24h: String?
var percentChange7d: String?
var priceBtc: String?
var priceUsd: String?
var rank: Int?
var symbol: String?
var tsupply: Int?
var volume24: Double?
var volume24a: Double?
enum CodingKeys: String, CodingKey {
case csupply = "csupply"
case id = "id"
case marketCapUsd = "market_cap_usd"
case msupply = "msupply"
case name = "name"
case nameid = "nameid"
case percentChange1h = "percent_change_1h"
case percentChange24h = "percent_change_24h"
case percentChange7d = "percent_change_7d"
case priceBtc = "price_btc"
case priceUsd = "price_usd"
case rank = "rank"
case symbol = "symbol"
case tsupply = "tsupply"
case volume24 = "volume24"
case volume24a = "volume24a"
}
}
And my network function work like below.
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(.failure(error!))
return
}
guard let data = data else {
completion(.failure(error ?? NSError()))
return
}
let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
guard let model = try? JSONDecoder().decode(CoinModel.self, from: data) else {
completion(.success([]))
return
}
completion(.success(model.data ?? []))
}.resume()
As I said before, json object is not nil and I got like below.
[({
csupply = "435032301.00";
id = 33285;
"market_cap_usd" = "435337535.24";
msupply = "";
name = "USD Coin";
nameid = "usd-coin";
"percent_change_1h" = "0.01";
"percent_change_24h" = "0.19";
"percent_change_7d" = "0.16";
"price_btc" = "0.000023";
"price_usd" = "1.00";
rank = 100;
symbol = USDC;
tsupply = 435032301;
volume24 = "11520659193.03477";
volume24a = "13684311331.83874";
}
)
, "info": {
"coins_num" = 5952;
time = 1621160044;
}]
I can handle the JSON with JSONSerialization but I could not find any reason for parse error in the JSONDecoder way. I think I did not see the problem. Thanks for any advice and helps.
Edit:
I solved it with several changes in my Codable class, I made a mistake when I describing model for id, csupply and etc.
var csupply: String?
var msupply: String?
var tsupply: String?
You have several mistakes on defining object. For example you have defined id, msupply is a integer but they are string. You have defined the volume24 and volume24a is string but they are not. You need to fix all of these probably thats why you can't parse it.
All wrong defined parameters are id, mSupply, volume24, volume24a, tSupply for coin object and for the info object you need to convert time to Int aswell.
In Swift, using Codable struct and CodingKeys emum,
If i have a Coordinate object, how can i get the latitude and longitude CodingKeys values, as an array ["1","2"]
struct Coordinate: Codable {
var latitude: Bool?
var longitude: Bool?
var elevation: Bool?
enum CodingKeys: String, CodingKey {
case latitude = "1"
case longitude = "2"
case elevation = "3"
}
}
And how to get all the CodingKeys values, only for the variables that are true ?
for example if longitude and elevation are set to true, I will get the array ["2,"3"]
CaseIterable will be helpful
struct Coordinate: Codable {
var latitude: Bool?
var longitude: Bool?
var elevation: Bool?
enum CodingKeys: String, CodingKey, CaseIterable {
case latitude = "1"
case longitude = "2"
case elevation = "3"
}
var allKeys: [String] {
CodingKeys.allCases.map { $0.stringValue }
}
}
If I understand correctly, something like this might work:
let myCoordinates = Coordinate(latitude: true, longitude: false, elevation: true)
var myArray: [String] = []
if myCoordinates.latitude ?? false {
myArray.append("1")
}
if myCoordinates.longitude ?? false {
myArray.append("2")
}
if myCoordinates.elevation ?? false {
myArray.append("3")
}
Also, thanks! I didn’t know Codable existed and now I think I might use it on my project!
How would I achieve this setup in Firebase Realtime Database with Swift:
Database hierarchy
Currently, I am doing this by storing the larger element (with properties familyKey, geofences, and phoneNumbers) as a custom object. Also, the geofences property itself is an array of custom objects. I get an NSException doing this in the described fashion. How else would I go about doing this?
var tempGeofences = [GeofenceData]()
tempGeofences.append(GeofenceData(name: "Hello WOrld", latitude: 0, longitude: 0, radius: 1000))
let familyKey:String = String(Int.random(in: 1000...99999))
let uid:String = Auth.auth().currentUser!.uid
let phoneNumber = "1111111111"
let parent = Parent(phoneNumber: phoneNumber, familyKey: familyKey, geofences: tempGeofences)
databaseRef.child(uid).setValue(parent)
The NSException is thrown on this line:
databaseRef.child(uid).setValue(parent)
Parent class:
import Foundation
public class Parent {
var phoneNumber: String?
var familyKey: String?
var geofences: [GeofenceData]?
init() {
self.phoneNumber = ""
self.familyKey = ""
self.geofences = nil
}
init(phoneNumber: String?, familyKey: String?, geofences:[GeofenceData]) {
self.phoneNumber = phoneNumber
self.familyKey = familyKey
self.geofences = geofences
}
public func getPhoneNumber() -> String {
return phoneNumber!
}
public func getFamilyKey() -> String {
return familyKey!
}
public func getGeofences() -> [GeofenceData] {
return geofences!
}
// left off here, trying to send geofence object to firebase
public func toDictionary() -> Any {
return ["familyKey": familyKey, "geofences": geofences, "phoneNumber": phoneNumber]
}
}
And the GeofenceData class:
import Foundation
import Firebase
public class GeofenceData {
var name: String?
var latitude: Double?
var longitude: Double?
var radius: Float?
init() {
}
init(name: String?, latitude: Double, longitude: Double, radius: Float) {
self.name = name
self.latitude = latitude
self.longitude = longitude
self.radius = radius
}
// left off here, trying to send geofence object to firebase
public func toDictionary() -> Any {
return ["name": name, "latitude": latitude, "longitude": longitude, "radius": radius]
}
public func getName() -> String {
return name!
}
public func getLatitude() -> Double {
return latitude!
}
public func getLongitude() -> Double {
return longitude!
}
public func getRadius() -> Float {
return radius!
}
public func setName(name: String?) {
self.name = name
}
public func saveToFirebase(reference: DatabaseReference) {
let dict = ["name": name, "latitude": latitude, "longitude": longitude, "radius": radius] as Any
reference.child("geofences").child("0").setValue(dict)
}
}
Parent is not an object that Firebase recognizes so it throws an error.
The Firebase guide Reading & Writing Data shows the four types of objects that can be written; String, Number, Dictionary, Array.
One solution is to build a function into the class that returns the data you want to write.
public class Parent {
var phoneNumber: String?
var familyKey: String?
var geofences: [GeofenceData]?
init() {
self.phoneNumber = ""
self.familyKey = ""
self.geofences = nil
}
//other init functions
func getParentDict() -> [String: Any] {
let geoDict = ["name": name,
"latitude": latitude,
"longitude": longitude,
"radius": radius
]
let zeroNode = ["0": geoDict]
let dictForFirebase: [String: Any] = [
"phoneNumber": phoneNumber,
"familyKey": familyKey,
"geofences": zeroNode
]
return dictForFirebase
}
}
and in practice
var tempGeofences = [GeofenceData]()
tempGeofences.append(GeofenceData(name: "Hello WOrld", latitude: 0, longitude: 0, radius: 1000))
let familyKey:String = String(Int.random(in: 1000...99999))
let uid:String = Auth.auth().currentUser!.uid
let phoneNumber = "1111111111"
let parent = Parent(phoneNumber: phoneNumber, familyKey: familyKey, geofences: tempGeofences)
let parentDict = parent.getParentDict
databaseRef.child(uid).setValue(parentDict)
However, one concern is the child node with "0" as the key. That looks like you may be using an array. If there's a good reason that's fine but there are usually much better alternatives to using array's in NoSQL databases. See the legacy but still accurate Firebase post called Arrays Are Evil
EDIT:
Per a comment/question 'how to add another child node following the "0" node"
Assume we know the parent node, qSaEE..., lets add a "1" node
let parentNode = "qSaEE..."
let geofenceRef = firebaseRef.child(parentNode).child("geofences")
let geoDict = ["name": name,
"latitude": latitude,
"longitude": longitude,
"radius": radius
]
let oneNode = ["1": geoDict]
geofenceNode.setValue(oneNode)
I have used JSON structs before and managed to get them working with a different API, but this API's JSON data is slightly different, it seems to encompass the data in an array called 'List'. I assume it is the Structs below that are in the incorrect format? As when I run the app, I don't get any error messages, but the Label value that I am trying to change, does not change, nor does the value of 'Test' get printed to the console. I am trying to call the Description value and print it to a label.
JSON Structs below:
struct MyForecast : Decodable {
let cod : String
let message : Double
let cnt : Int
let list : [List]
let city : Cityy
let coordinate : Coordi
}
struct Coordi : Decodable {
let lat, lon : Double
}
struct Cityy : Decodable {
let id, population : Int
let name, country : String
let coord : Coordinate
}
struct Mainn : Decodable {
let temp, tempMin, tempMax : Double
let seaLevel, grndLevel, tempKf: Double
let pressure, humidity : Int
}
struct Windd : Decodable {
let speed : Double
let deg : Double
}
struct Weatherr : Decodable {
let id : Int
let icon : String
let main : MainEnum
let description: String
}
struct List : Decodable {
let dt : Date
let main : MainForecast
let weather : [Weatherr]
let clouds : Cloudss
let wind : Windd
let sys : Syss
let dtTxt : String
let rain: Rainn?
let city: Cityy
}
struct Syss : Decodable {
let pod: Pod
}
struct MainForecast : Decodable {
let temp, tempMin, tempMax, pressure, seaLevel, grndLevel, humidity, tempKf : Double?
}
struct Cloudss : Decodable {
let all : Int
}
struct Rainn: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
enum Pod: String, Codable {
case d = "d"
case n = "n"
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
ViewController below:
class ForecastViewController: UIViewController {
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
guard let APIUrl = URL (string: "https://api.openweathermap.org/data/2.5/forecast?q=London&APPID=***APIKEY***&units=metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoderr = JSONDecoder()
do {
decoderr.keyDecodingStrategy = .convertFromSnakeCase
decoderr.dateDecodingStrategy = .secondsSince1970
let forecastData = try decoderr.decode(MyForecast.self, from: data)
if let test = forecastData.list.first?.city.name { //using .first because Weather is stored in an array
let description = test.description
print(description)
DispatchQueue.main.async {
self.testLabel.text! = description
}
}
else
{
print("weather not found")
}
} catch {
print(error.localizedDescription)
}
}.resume()
Your structs are were wrong before you edited the question.
The 5 day / 3 hour Forecast API of openweathermap.org sends a different JSON structure as the Current Weather Data.
You can create the structs very easy yourself:
Download the Data
Create a (JSON) string from the data
Copy the text
Open app.quicktype.io
Paste the text in the JSON text field on the left side
Add a suitable name for the root object.
quicktype.io creates the structs for you.
The forecast structs are (except Rain there are no optionals at all)
struct MyForecast : Decodable {
let cod : String
let message : Double
let cnt : Int
let list : [List]
let city : City
}
struct Coordinate : Decodable {
let lat, lon : Double
}
struct City : Decodable {
let id, population : Int
let name, country : String
let coord : Coordinate
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double
let seaLevel, grndLevel, tempKf: Double
let pressure, humidity : Int
}
struct Wind : Decodable {
let speed : Double
let deg : Double
}
struct Weather : Decodable {
let id : Int
let icon : String
let main : MainEnum
let description: String
}
struct List : Decodable {
let dt : Date
let main : MainForecast
let weather : [Weather]
let clouds : Clouds
let wind : Wind
let sys : Sys
let dtTxt : String
let rain: Rain?
}
struct Sys : Decodable {
let pod: Pod
}
struct MainForecast : Decodable {
let temp, tempMin, tempMax, pressure, seaLevel, grndLevel, humidity, tempKf : Double
}
struct Clouds : Decodable {
let all : Int
}
struct Rain: Codable {
let the3H: Double?
enum CodingKeys: String, CodingKey {
case the3H = "3h"
}
}
enum Pod: String, Codable {
case d, n
}
enum MainEnum: String, Codable {
case clear = "Clear"
case clouds = "Clouds"
case rain = "Rain"
}
To decode the structs you have to add date and key decoding strategies.
List and Weather are arrays
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .secondsSince1970
let forecastData = try decoder.decode(MyForecast.self, from: data)
if let test = forecastData.list.first?.weather.first? { //using .first because Weather is stored in an array
let description = test.description
print(description)
DispatchQueue.main.async {
self.testLabel.text! = description
}
} else { print("weather not found") }
I'm trying to parse data i've received from the server so as to display it into a UIPicker view. but it's too complex for me to parse and get it displayed into UIPIcker View. Whats the best way i can parse the following data into and make it ready for a UIPickerView.
This is the session trying to parse the information from the server
let url = NSURL(string: "http://dummy.com/api")!
let request = URLRequest(url: url as URL)
URLSession.shared.dataTask(with: request) { data, response, error in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
guard let parseJSON = json else{
print("Error While Parsing")
return
}
print(parseJSON)
let responseDic = parseJSON["response"] as? NSDictionary
let utilityCode = responseDic?["utility_code"] as? String
if utilityCode == "AFRICELL" {
let africellPackages = responseDic?["packages"] as! NSArray
print(africellPackages)
}
}catch{
return
}
}
}.resume()
The following data is the response from the server when the GET request is made.
{
"status": "OK",
"response": [
{
"utility_code": "AIRTEL",
"packages": [
{
"package_id": 33,
"package_name": "Daily 10MB",
"package_code": "6000",
"package_price": 300
},
{
"package_id": 34,
"package_name": "Daily 20MB",
"package_code": "6002",
"package_price": 500
},
{
"package_id": 65,
"package_name": "Weekly Roaming 200MB",
"package_code": "6030",
"package_price": 100000
}
]
},
{
"utility_code": "AFRICELL",
"packages": [
{
"package_id": 68,
"package_name": "Daily 10 MB",
"package_code": "5000",
"package_price": 290
}
]
},
{
"utility_code": "SMART",
"packages": [
{
"package_id": 69,
"package_name": "Daily 50 MB",
"package_code": "8000",
"package_price": 500
}
]
},
{
"utility_code": "SMILE",
"packages": [
{
"package_id": 70,
"package_name": "Smile 1GB",
"package_code": "7006",
"package_price": 32000
}
]
}
]
}
Cheers and thanks for the help!
All examples below are provided without error checking for brevity. For production code, you should handle errors properly.
Swift 3 without any external framework
Most of the work to decode JSON manually involves defining the data model:
struct JSONResponse {
var status: String
var response: [Utility]
init(jsonDict: [String: AnyObject]) {
self.status = jsonDict["status"] as! String
self.response = [Utility]()
let response = jsonDict["response"] as! [AnyObject]
for r in response.map({ $0 as! [String: AnyObject] }) {
let utility = Utility(jsonDict: r)
self.response.append(utility)
}
}
}
struct Utility {
var code: String
var packages: [Package]
init(jsonDict: [String: AnyObject]) {
self.code = jsonDict["utility_code"] as! String
self.packages = [Package]()
let packages = jsonDict["packages"] as! [AnyObject]
for p in packages.map({ $0 as! [String: AnyObject] }) {
let package = Package(jsonDict: p)
self.packages.append(package)
}
}
}
struct Package {
var id: Int
var name: String
var code: String
var price: Int
init(jsonDict: [String: AnyObject]) {
self.id = jsonDict["package_id"] as! Int
self.name = jsonDict["package_name"] as! String
self.code = jsonDict["package_code"] as! String
self.price = jsonDict["package_price"] as! Int
}
}
And how to use it:
let jsonDict = try! JSONSerialization.jsonObject(with: data) as! [String: AnyObject]
let jsonResponse = JSONResponse(jsonDict: jsonDict)
let utilities = jsonResponse.response
print(utilities)
Swift 3 with ObjectMapper
Decoding JSON is it of a pain in standard Swift. You need an external JSON framework like ObjectMapper to ease the pain in mapping between the JSON data and your data model. Install ObjectMapper from CocoaPod or Carthage.
First, define your data model in a separate file:
import ObjectMapper
struct JSONResponse : Mappable {
var status: String?
var response: [Utility]?
init?(map: Map) { }
mutating func mapping(map: Map) {
self.status <- map["status"]
self.response <- map["response"]
}
}
struct Utility : Mappable {
var code: String?
var packages: [Package]?
init?(map: Map) { }
mutating func mapping(map: Map) {
self.code <- map["code"]
self.packages <- map["packages"]
}
}
struct Package : Mappable {
var id: Int?
var name: String?
var code: String?
var price: Int?
init?(map: Map) { }
mutating func mapping(map: Map) {
self.id <- map["package_id"]
self.name <- map["package_name"]
self.code <- map["package_code"]
self.price <- map["package_price"]
}
}
Then you can use it to map JSON to your object like this:
// data is what your get in the dataTask's completion block
let jsonString = String(data: data, encoding: .utf8)!
if let jsonResponse = JSONResponse(JSONString: jsonString),
let utilities = jsonResponse.response {
print(utilities)
}
Everything has to be declared optional because you don't know if the value will be in the JSON string or not.
In Swift 4
Things get a lot simpler in Swift 4 with the new Encodable and Decodable protocols. Both combined to form the Encodable protocol (yes, protocol composition is a new thing in Swift 4). You no longer need ObjectMapper in Swift 4.
You still need to define your data model first:
struct JSONResponse : Codable {
var status: String
var response: [Utility]
}
struct Utility : Codable {
var code: String
var packages: [Package]
private enum CodingKeys: String, CodingKey {
case code = "utility_code"
case packages
}
}
struct Package : Codable {
var id: Int
var name: String
var code: String
var price: Int
private enum CodingKeys: String, CodingKey {
case id = "package_id"
case name = "package_name"
case code = "package_code"
case price = "package_price"
}
}
And here's how you use the new JSONDecoder struct in Swift 4:
let jsonRepsonse = try! JSONDecoder().decode(JSONResponse.self, from: data)
let utilities = jsonRepsonse.response
print(utilities)
So what's going on here?
Both ObjectMapper and Codable map the values in JSON to your data model.
In ObjectMapper, you have to explicitly list which property maps to which value in JSON in a function named mapping(map:):
mutating func mapping(map: Map) {
self.id <- map["package_id"]
self.name <- map["package_name"]
self.code <- map["package_code"]
self.price <- map["package_price"]
}
<- is the "mapping operator" defined by ObjectMapper (it's not in the Standard Library).
With Swift 4's Codable, the compiler automates a lot of things for you, which makes it appear magical and confusing at first. Essentially you define your mappings in an enum called CodingKeys:
struct JSONResponse : Codable {
var status: String
var response: [Utility]
// Define your JSON mappings here
private enum CodingKeys: String, CodingKey {
case status = "status"
case response = "response"
}
}
struct Package : Codable {
var id: Int
var name: String
var code: String
var price: Int
// Define your JSON mappings here
private enum CodingKeys: String, CodingKey {
case id = "package_id"
case name = "package_name"
case code = "package_code"
case price = "package_price"
}
}
If your JSON keys are the same as your property name, like in the JSONResponse struct, you don't have to define the explicit value for each case and you can simply write:
struct JSONResponse : Codable {
var status: String
var response: [Utility]
private enum CodingKeys: String, CodingKey {
case status // no explicit value here
case response
}
}
Better yet, since every JSON key is the same as your property name, you can omit the CodingKeys enum all together and let the compiler handle that for you:
// All you need to do is to conform it to Codable
// CodingKeys is generated automatically
struct JSONResponse : Codable {
var status: String
var response: [Utility]
}
To learn more about Codable, watch the What's new in Foundation session from WWDC 2017.