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.
Related
Im working on a JSON payload with my object payload. but im having trouble encoding object inside object.
My Payload class was this
class ConversationPayload :BaseObject {
var title : String? = ""
var messageDict: MessagePayload = MessagePayload()
var participants: [Int32] = []
var type: String = ""
enum CodingKeys: String, CodingKey {
case title = "title"
case messageDict = "message"
case participants = "participants"
case type = "type"
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if title != nil {
try container.encode(title, forKey: .title)
}
try container.encode(messageDict, forKey: .messageDict)
try container.encode(participants, forKey: .participants)
try container.encode(type, forKey: .type)
}
}
class MessagePayload: BaseObject {
var body : String = ""
var isVideocallInvite: Bool = false
var attachmentsPayload: MessageAttachmentPayload? = nil
enum CodingKeys: String, CodingKey {
case body = "body"
case isVideocallInvite = "is_videocall_invite"
case attachmentsPayload = "attachment"
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(body, forKey: .body)
try container.encode(isVideocallInvite, forKey: .isVideocallInvite)
if attachmentsPayload != nil {
try container.encode(attachmentsPayload, forKey: .attachmentsPayload)
}
}
}
class MessageAttachmentPayload: BaseObject {
var photo : String = ""
var photoType : String = "jpg"
}
BaseObject was this
class BaseObject:Codable{}
What i want to get in json payload was something like this
{"message": {"body": "body_string", "is_videocall_invite": 1}, "participants" : [user-id], "type" : "converstation_type","title":"title"}
anyone know whats wrong with my payload class? not that familiar yet on codable. thanks in advance.
I'm not sure what constrains you have here, but I would simplify all of this down. Keep the JSON data models as close to the JSON as you can get.
struct ConversationJsonModel: Codable {
var title: String?
var message: MessageJsonModel
var participants: [Int]
var type: String
}
struct MessageJsonModel: Codable {
var body: String
var is_videocall_invite: Int
var attachment: AttachmentJsonModel?
}
struct AttachmentJsonModel: Codable {
var photo: String
var photo_type: String // <-- assuming photo_type is the JSON member name.
}
If you need a view model or some other kind of local data model, then the two parts are separate.
class BaseObject {}
class ConversationPayload: BaseObject {
var title : String? = ""
var messageDict: MessagePayload = MessagePayload()
var participants: [Int32] = []
var type: String = ""
func makeConversationJsonModel() -> ConversationJsonModel {
ConversationJsonModel(title: title,
message: messageDict.makeMessageJsonModel(),
participants: participants.map { Int($0) },
type: type)
}
}
class MessagePayload: BaseObject {
var body : String = ""
var isVideocallInvite: Bool = false
var attachmentsPayload: MessageAttachmentPayload? = nil
func makeMessageJsonModel() -> MessageJsonModel {
MessageJsonModel(body: body,
is_videocall_invite: isVideocallInvite ? 1 : 0,
attachment: attachmentsPayload?.makeAttachmentJsonModel())
}
}
class MessageAttachmentPayload: BaseObject {
var photo : String = ""
var photoType : String = "jpg"
func makeAttachmentJsonModel() -> AttachmentJsonModel {
AttachmentJsonModel(photo: photo, photo_type: photoType)
}
}
Finally, encoding your JSON
let conversationPayload = ConversationPayload()
let json = try? JSONEncoder().encode(conversationPayload.makeConversationJsonModel())
This allows for clean separation between the JSON representation and the payload model. For example, in the JSON, is_videocall_invite is an Int (0 or 1); meanwhile, in the payload model, isVideocallInvite is a Bool.
What is the issue? I just tested your class, it has some minor issues but it does conforming to codable and it encoded without issue.
In playground:
var conversation = ConversationPayload()
var message = MessagePayload()
message.body = "message body"
message.isVideocallInvite = true
var attachment = MessageAttachmentPayload()
attachment.photo = "attachment_file"
message.attachmentsPayload = attachment
conversation.messageDict = message
conversation.title = "Title"
conversation.participants = [1, 2, 3, 4]
conversation.type = "Conversation Type"
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(conversation)
print(String(data: data, encoding: .utf8)!)
The output looks like the following:
{
"participants" : [
1,
2,
3,
4
],
"message" : {
"is_videocall_invite" : true,
"attachment" : {
"photo" : "attachment_file",
"photoType" : "jpg"
},
"body" : "message body"
},
"title" : "Title",
"type" : "Conversation Type"
}
I have added encode and CodingKey in MessageAttachmentPayload class to encode the values.
Isn't it what you were expecting?
how can I set an array of Images to an images variable of the Car class
my json:
{
"Description": "test comment",
"GalleryId": 5548,
"ShortCode": "rzswig",
"Images": [
{
"Id": 9742,
"Link": "https://url/Images/5548/image9742_x560.jpg"
},
{
"Id": 9749,
"Link": "https://url/Images/5548/image9749_x560.jpg"
},
{
"Id": 9746,
"Link": "https://url/Images/5548/image9746_x560.jpg"
}
]
}
my class :
class Car: Hashable {
var shortCode: String
var description: String
var galleryId: Int
var imageItems: [ImageItem]?
init(response: JSON) {
shortCode = response["ShortCode"].stringValue
description = response["Description"].stringValue
galleryId = response["GalleryId"].intValue
imageItems = response["Images"].arrayObject.map {$0} as? [ImageItem]
}
init(shortCode: String, description: String, galleryId: Int, imageItems: [ImageItem]?) {
self.description = description
self.shortCode = shortCode
self.galleryId = galleryId
self.imageItems = imageItems
}
}
struct ImageItem {
var id: Int
var link: String
}
variant:
imageItems = response["Images"].arrayObject.map {$0} as? [ImageItem]
doesn't work for me
If you want to keep using SwiftyJSON, you can add an initialiser to ImageItem that takes a JSON, just like you did with Car:
init(response: JSON) {
id = response["Id"].intValue
link = response["Link"].stringValue
}
You might also want to add the autogenerated member wise initialiser back if you want it.
Then, to initialise imageItems, do:
imageItems = response["Images"].array?.map(ImageItem.init(response:))
This whole thing would be a lot easier if you used the Codable API built in to Swift.
Just conform both Car and ImageItem to Codable:
class Car: Codable {
var shortCode: String
var description: String
var galleryId: Int
var imageItems: [ImageItem]?
enum CodingKeys: String, CodingKey {
case shortCode = "ShortCode"
case description = "Description"
case galleryId = "GalleryId"
case imageItems = "Images"
}
}
struct ImageItem: Codable {
var id: Int
var link: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case link = "Link"
}
}
And then do this to deserialise the json:
let car = JSONDecoder().decode(Car.self, data: jsonData) // jsonData is of type "Data"
I think the problem is that you map it as array of Image item
imageItems = response["Images"].arrayObject.map {$0} as? ImageItem
try response["images"].arrayValue
I am stuck on parsing JSON. The structure is really hard. I was trying this with a decodable approach.
import UIKit
struct WeatherItem: Decodable {
let title: String?
let value: String?
let condition: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("hello")
let jsonUrlString = "http://virtualflight.ddns.net/api/weather.php?icao=ehrd"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
var arr = [WeatherItem]()
do {
let res = try JSONDecoder().decode([String:[[String]]].self, from: data)
let content = res["title"]!
content.forEach {
if $0.count >= 3 {
arr.append(WeatherItem(title:$0[0],value:$0[1],condition:$0[2]))
}
}
print(arr)
} catch {
print(error)
}
}
}
}
The json is the following:
{
"temperature": {
"value_c": 11,
"value_f": 285,
"condition": "Good",
"value_app": "11 \u00b0C (285 \u00b0F)"
},
"visibility": {
"value_km": 10,
"value_m": 6.2,
"condition": "Good",
"value_app": "10 KM (6.2 Mi)"
},
"pressure": {
"value_hg": 29.4,
"value_hpa": 996,
"condition": "Good",
"value_app": "29.4 inHg (996 hPa)"
},
"wind": {
"value_kts": 20,
"value_kmh": 37,
"value_heading": 280,
"condition": "Bad",
"value_app": "280\u00b0 at 20 KTS (37 Km\/H)"
},
"station": "EHRD",
"metar": "EHRD 141355Z AUTO 28020KT 250V320 9999 SCT038 BKN043 BKN048 11\/07 Q0996 NOSIG",
"remarks": "NOSIG",
"weather_page_ios_simple": [
[
"Temperature",
"11 \u00b0C (285 \u00b0F)",
"Good"
],
[
"Visibility",
"10 KM (6.2 Mi)",
"Good"
],
[
"Pressure",
"29.4 inHg (996 hPa)",
"Good"
],
[
"Wind",
"280\u00b0 at 20 KTS (37 Km\/H)",
"Bad"
],
[
"Metar",
"EHRD 141355Z AUTO 28020KT 250V320 9999 SCT038 BKN043 BKN048 11\/07 Q0996 NOSIG",
"Unknown"
],
[
"Remarks",
"NOSIG",
"Unknown"
],
[
"Station",
"EHRD",
"Unknown"
],
[
"UICell",
"iOS 12",
"siri_weather_cell"
]
]
}
any ideas how to do this?? I only need the last array, weather_page_ios_simple.
Have a look at https://app.quicktype.io it will give you the data structure for your JSON.
import Foundation
struct Welcome: Codable {
let temperature: Temperature
let visibility: Visibility
let pressure: Pressure
let wind: Wind
let station, metar, remarks: String
let weatherPageIosSimple: [[String]]
enum CodingKeys: String, CodingKey {
case temperature, visibility, pressure, wind, station, metar, remarks
case weatherPageIosSimple = "weather_page_ios_simple"
}
}
struct Pressure: Codable {
let valueHg: Double
let valueHpa: Int
let condition, valueApp: String
enum CodingKeys: String, CodingKey {
case valueHg = "value_hg"
case valueHpa = "value_hpa"
case condition
case valueApp = "value_app"
}
}
struct Temperature: Codable {
let valueC, valueF: Int
let condition, valueApp: String
enum CodingKeys: String, CodingKey {
case valueC = "value_c"
case valueF = "value_f"
case condition
case valueApp = "value_app"
}
}
struct Visibility: Codable {
let valueKM: Int
let valueM: Double
let condition, valueApp: String
enum CodingKeys: String, CodingKey {
case valueKM = "value_km"
case valueM = "value_m"
case condition
case valueApp = "value_app"
}
}
struct Wind: Codable {
let valueKts, valueKmh, valueHeading: Int
let condition, valueApp: String
enum CodingKeys: String, CodingKey {
case valueKts = "value_kts"
case valueKmh = "value_kmh"
case valueHeading = "value_heading"
case condition
case valueApp = "value_app"
}
}
If you only need the bottom array of data, you shouldn't need to put everything into the decoded struct. Just decode the part of the response you want and pull the data from there. Also, that array of data isn't really parsing JSON without keys. Its just an array of strings, you'll have to rely on the fact that index 0 is always title, 1 is always value, and 2 is always condition. Just do some validation to make sure it fits your needs. Something like this (UNTESTED)
struct WeatherItem {
let title: String?
let value: String?
let condition: String?
init(title: String?, value: String?, condition: String?) {
self.title = title
self.value = value
self.condition = condition
}
}
struct WeatherResponse: Decodable {
var weatherItems: [WeatherItem]
private enum CodingKeys: String, CodingKey {
case weatherItems = "weather_page_ios_simple"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let weatherItemArrays = try container.decode([[String]].self, forKey: .weatherItems)
weatherItems = []
for weatherItemArray in weatherItemArrays {
var title: String?
if weatherItemArray.count > 0 {
title = weatherItemArray[0]
}
var value: String?
if weatherItemArray.count > 1 {
value = weatherItemArray[1]
}
var condition: String?
if weatherItemArray.count > 2 {
condition = weatherItemArray[2]
}
weatherItems.append(WeatherItem(title: title, value: value, condition: condition))
}
}
}
And then when you get your api response get the weather items out with something like
do {
let weatherResponse = try JSONDecoder().decode(WeatherResponse.self, from: <YOUR API RESPONSE DATA>)
let weatherItems = weatherResponse.weatherItems
<DO WHATEVER YOU WANT WITH THE WEATHER ITEMS>
} catch let error {
print(error)
}
This is my JSON response:
[
[
{
"id": 22,
"request_id": "rqst5c12fc9e856ae1.06631647",
"business_name": "Code Viable",
"business_email": "code#viable.com",
"title": "Apache Load/Ubuntu",
}
],
[
{
"id": 24,
"request_id": "rqst5c130cae6f7609.41056231",
"business_name": "Code Viable",
"business_email": "code#viable.com",
"title": "Load",
}
]
]
This JSON structure got an array inside of an array, the object of the inner array is what I am trying to parse. Here is the my mapper:
struct JobResponseDataObject: Mappable {
init?(map: Map) {
}
var id: Int?
var requestId: String?
var businessName: String?
var businessEmail: String?
mutating func mapping(map: Map) {
id <- map["id"]
requestId <- map["request_id"]
businessName <- map["business_name"]
businessEmail <- map["business_email"]
}
}
I have tried create another mapper struct to hold the array of objects [JobResponseDataObject] and use Alamofire's responseArray with it, but it didn't work. I have also tried prefixing my json id with 0. but that didn't work too. Please help
Thank
So here's the deal...Codable is a pretty cool protocol from Apple to handle parsing JSON responses from APIs. What you're getting back is an array of arrays, so your stuff's gonna be look like this:
[[ResponseObject]]
So anyway, you'd make a struct of your object, like so:
struct ResponseObject: Codable {
let id: Int?
let requestId: String?
let businessName: String?
let businessEmail: String?
let title: String?
}
You'll note I changed the key name a bit (instead of request_id, I used requestId). The reason is JSONDecoder has a property called keyDecodingStrategy which presents an enum of canned decoding strategies you can select from. You'd do convertFromSnakeCase.
Here's code you can dump into a playground to tinker with. Basically, declare your struct, match it up to whatever the keys are in your JSON, declare a decoder, feed it a decoding strategy, and then decode it.
Here's how you could do an Alamofire call:
private let backgroundThread = DispatchQueue(label: "background",
qos: .userInitiated,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
Alamofire.request(url).responseJSON(queue: backgroundThread) { (response) in
guard response.result.error == nil else {
print("💥KABOOM!💥")
return
}
if let data = response.data {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let parsedResponse = try decoder.decode([[ResponseObject]].self, from: data)
print(parsedResponse)
} catch {
print(error.localizedDescription)
}
}
}
Here's code you can chuck in a playground.
import UIKit
let json = """
[
[
{
"id": 22,
"request_id": "rqst5c12fc9e856ae1.06631647",
"business_name": "Code Viable",
"business_email": "code#viable.com",
"title": "Apache Load/Ubuntu",
}
],
[
{
"id": 24,
"request_id": "rqst5c130cae6f7609.41056231",
"business_name": "Code Viable",
"business_email": "code#viable.com",
"title": "Load",
}
]
]
"""
struct ResponseObject: Codable {
let id: Int?
let requestId: String?
let businessName: String?
let businessEmail: String?
let title: String?
}
if let data = json.data(using: .utf8) {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let parsedResponse = try decoder.decode([[ResponseObject]].self, from: data)
print(parsedResponse)
} catch {
print(error.localizedDescription)
}
}
You should use this JobResponseDataObject struct as [[JobResponseDataObject]] instead of [JobResponseDataObject] - where you are making a property using this struct in your parent struct or class.
You can use Codable here for mapping the JSON response, The JobResponseDataObject struct should look like,
struct JobResponseDataObject: Codable {
var id: Int?
var requestId: String?
var businessName: String?
var businessEmail: String?
var title: String?
private enum CodingKeys: String, CodingKey {
case id = "id"
case requestId = "request_id"
case businessName = "business_name"
case businessEmail = "business_email"
case title = "title"
}
}
let json = JSON(responseJSON: jsonData)
do {
if let value = try? json.rawData(){
let response = try! JSONDecoder().decode([[JobResponseDataObject]].self, from: value)
}
} catch {
print(error.localizedDescription)
}
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.