Store JSON data into model class array in Swift 4 - ios

I want to store the fetched JSON data to my model class array propertyArray. Some how I got the JSON data using Alamofire library and now I want to parse that data to the properties of PropertyList model class. I am not able to parse JSON data to propertyArray. I referred many other solutions to this question on stack overflow but did not get any appropriate solution.
Declared Model Class Array
var propertyArray: [PropertyList]? = []
Alamofire Function
func dataPass() {
print("Landlord id is \(land.id)")
let para: Parameters = ["id": land.id]
Alamofire.request(URL_Landlord_Property_List, method: .post, parameters: para).responseJSON { response in
if let dictionary = response.result.value as? [String : AnyObject]{
var prop = PropertyList()
let data = dictionary["data"] as! [Any]
print(data)
}
}
}
PropertyList
import Foundation
struct PropertyList{
var property_id: String?
var user_id: String?
var property_code: String?
var property_added_date: String?
var property_updated_date: String?
var property_termination_date: String?
var property_ASYS_no: String?
var propertyCode: String?
var property_address_1:String?
var property_address_2: String?
var property_address_3: String?
var property_city: String?
var property_cluster:String?
var property_area:String?
var property_postcode: String?
var property_landlord_ref_code: String?
var property_landlord_id: String?
}
JSON Data
{
"success": true,
"data": [
{
"property_id": "1",
"user_id": "1",
"property_code": "0001",
"property_added_date": "2017-12-13",
"property_updated_date": "2017-12-13 00:00:00",
"property_termination_date": null,
"property_ASYS_no": "ASYS 001",
"propertyCode": "0001-PUNE1",
"property_address_1": "PUNE1",
"property_address_2": "PUNE2",
"property_address_3": "PUNE3",
"property_city": "PUNE",
"property_cluster": "1",
"property_area": "1",
"property_postcode": "424031",
"property_landlord_ref_code": null,
"property_landlord_id": "1"
},
{
"property_id": "2",
"user_id": "1",
"property_code": "0002",
"property_added_date": "2017-12-14",
"property_updated_date": "2017-12-18 00:00:00",
"property_termination_date": null,
"property_ASYS_no": "asys 0200",
"propertyCode": "0002-hadpasar1",
"property_address_1": "hadpasar1",
"property_address_2": "hadpasar2",
"property_address_3": "hadpasar3",
"property_city": "pune",
"property_cluster": "9",
"property_area": "2",
"property_postcode": "012121",
"property_landlord_ref_code": null,
"property_landlord_id": "1"
}
]
}

Looks like your JSON is an array that contains dictionaries. (Key-Value data structure)
This should do the trick.
let data = dictionary["data"] as! [[String: Any]]
Then parse it inside your constructor:
struct PropertyList{
init(dict: [String: Any){
//parse here...
self.property_id = dict["property_id"] as! String
}
}
To add to array:
for dict in data{
let propertyList = PropertyList(dict: dict)
propertyArray.append(propertyList)
}

Related

How to send an array of JSON object as a parameter value in request body ios swift

Hello I'm using Invoice generator api JSON Input
https://invoice-generator.com/developers#json-input
In the documentation of the API it is mentioned that the items key takes an array of json objects as value.
Here is my swift dictionary representation of the body
let parameters: [String: Any] = [
"from":"Invoiced, Inc.",
"to": "Acme, Corp.",
"logo":"https://invoiced.com/img/logo-invoice.png",
"number":1,
"items":["name":"starter plan",
"quantity":1,
"unit_cost":99],
"notes":"Thanks for your business!"
]
The problem is I get my invoice and display it in PDFView when I send an empty item array as
item:[]
But when I send with some values in the array I get invalid response from the server.
How can I send an array of JSON objects as a value for item key in the API request body
You need to encode an object to JSON. First, you should create the object:
struct JSONParameters {
let from: String
let to: String
let logo: String
let number: Int
// ... all other parameters
}
Then, encode it to JSON and add it to the request body:
do {
let parameters = JSONParameters(from: "Invoiced Inc.", to:......) // Complete object
let json = try JSONEncoder().encode(parameters)
print("Encoded JSON: \(String(data: json, encoding: .utf8)!)")
var request = URLRequest(url: yourURLHere)
request.httpBody = json
let (data, response) = try await URLSession.shared.data(for: request) // Or whatever function you need to use
// Manage response
} catch {
print("\n-->> Error trying to encode : \(error) - \(String(describing: parameters))")
}
OUTPUT: -
[
"number": 1,
"to": "Acme, Corp.",
"notes": "Thanks for your business!",
"from": "Invoiced, Inc.",
"logo": "https://invoiced.com/img/logo-invoice.png",
"items": [
["quantity": 1, "name": "Starter plan", "unit_cost": 99],
["quantity": 1, "name": "Starter plan2", "unit_cost": 99]
]
]
struct BodyModel: Codable {
var from, to: String?
var logo: String?
var number: Int?
var items: [Item]?
var notes: String?
func toDict() -> [String:Any] {
var dictionary = [String:Any]()
if from != nil {
dictionary["from"] = from
}
if to != nil {
dictionary["to"] = to
}
if logo != nil {
dictionary["logo"] = logo
}
if number != nil {
dictionary["number"] = number
}
if items != nil {
var arrOfDict = [[String:Any]]()
for item in items! {
arrOfDict.append(item.toDict())
}
dictionary["items"] = arrOfDict
}
if notes != nil {
dictionary["notes"] = notes
}
return dictionary
}
}
struct Item: Codable {
var name: String?
var quantity, unit_cost: Int?
func toDict() -> [String:Any] {
var dictionary = [String:Any]()
if name != nil {
dictionary["name"] = name
}
if quantity != nil {
dictionary["quantity"] = quantity
}
if unit_cost != nil {
dictionary["unit_cost"] = unit_cost
}
return dictionary
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var arrItems = [Item]()
arrItems.append(Item(name: "Starter plan", quantity: 1, unit_cost: 99))
arrItems.append(Item(name: "Starter plan2", quantity: 1, unit_cost: 99))
let body = BodyModel(from: "Invoiced, Inc.",
to: "Acme, Corp.",
logo: "https://invoiced.com/img/logo-invoice.png",
number: 1,
items: arrItems,
notes: "Thanks for your business!")
let parameters: [String: Any] = body.toDict()
print(parameters)
}
}

Swift Codable JSON parse error with JSONDecoder

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.

Swift ObjectMapper: How to parse array inside of an array

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)
}

How to convert object array into Json array or Json string in ios swift?

I have an array which has set of objects var quiz_anwers_list = [QuizQu]() "QuizQu" is a Class which contains 2 variables
class QuizQu: Decodable {
var ques_id: String?
var their_answer: String?
}
Now am having,
for i in 0...self.quiz_anwers_list.count-1{
print(self.quiz_anwers_list[i].ques_id ?? "no val in ques id of \(i)")
print(self.quiz_anwers_list[i].their_answer ?? "no val in their_ans of \(i)")
}
The output of those print is:
14
correct_answer
15
correct_answer2
16
correct_answer2
17
correct_answer
Now how can I convert this into JsonArray or JSON String? I am a new to iOS.
Your class should probably be a struct and should conform to Encodable, not Decodable, if you plan to encode and decode you can use the Codable protocol which covers both cases.
Once you have done that, just use JSONEncoder to convert it to JSON data and then you can print it using String(bytes: Sequence, encoding: String.Encoding)
struct QuizQu: Codable {
var ques_id: String?
var their_answer: String?
}
let questions = [
QuizQu(ques_id: "1", their_answer: "2"),
QuizQu(ques_id: "2", their_answer: "2"),
QuizQu(ques_id: "3", their_answer: "1"),
QuizQu(ques_id: "4", their_answer: "4"),
QuizQu(ques_id: "5", their_answer: "3")
]
do {
let encoded = try JSONEncoder().encode(questions)
print(String(bytes: encoded, encoding: .utf8))
} catch {
print(error)
}
Output:
Optional("[{\"ques_id\":\"1\",\"their_answer\":\"2\"},{\"ques_id\":\"2\",\"their_answer\":\"2\"},{\"ques_id\":\"3\",\"their_answer\":\"1\"},{\"ques_id\":\"4\",\"their_answer\":\"4\"},{\"ques_id\":\"5\",\"their_answer\":\"3\"}]")
Note: the output String is escaped, hence the backslashes
You can do it with
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
let struct = QuizQu(ques_id: 1, their_answer: abc)
let jsonObj = struct.dictionary

My struct isn't working for decodable

been using swift 3 for sometime, till i updated my Xcode which came with swift 4, and with that i had to start using encodable decodable , but i'm having a hard time generating structs to decode. This is the json i'm trying to turn into an object
{
"status": "true",
"message": "Valid request",
"data": {
"user_id": "16",
"first_name": "Hamidouh",
"last_name": "Semix",
"other_name": null,
"fullname": "Hamidouh Semix",
"alias": null,
"about": "",
"sex_id": "1",
"sex": "Male",
"birth_date": "1989-08-17 00:00:00",
"relation_status_id": null,
"relation_status": null,
"relation_user_id": null,
"relation_user_first_name": null,
"relation_user_last_name": null,
"relation_user_fullname": null,
"location": null,
"contact": null,
"profile_pic": "698",
"profile_folder_name": "profile-picture",
"profile_pic_filename": "PROFILE-IMG-UPLOAD-1-16-20171222101217.jpg",
"cover_pic": "697",
"cover_folder_name": "cover-picture",
"cover_pic_filename": "COVER-IMG-UPLOAD-1-16-20171222100128.png",
"followers_list": {
"user_11": {
"id": "11",
"datetime": "2018-02-20 19:09:44"
}
},
"following_list": {
"user_1": {
"id": "1",
"datetime": "2018-03-01 09:53:24"
},
"user_3": {
"id": "3",
"datetime": "2018-02-19 09:18:18"
},
"user_24": {
"id": "24",
"datetime": "2017-12-22 09:58:17"
},
"user_260": {
"id": "260",
"datetime": "2018-02-19 09:18:16"
}
},
"mutual_list": {
"user_78": {
"id": "78",
"datetime": "2017-12-08 12:05:23"
}
},
"request_counter": "0",
"dream_destination_list": null,
"hidden_content_list": null,
"email": "semixss.hamidouh#gmail.com",
"username": null,
"password": "84bcec2f89a4f8cdd17b4a98d1a6cbf69bc9efe657d36a09b2d423fa3030772ab8ba9e24",
"salt": "owkLO",
"social_media_auth": null,
"social_media_id": null,
"date_created": "2017-08-18 14:00:22",
"last_seen": "2018-03-16 13:53:57",
"is_suspended": null,
"is_notified": null,
"approve_token": "8f82fbac62ded7260a3faa45460719a1390e3e216c6ebf3c4794f2da627138b9",
"device_token": null,
"language_id": null,
"language_machine_name": null,
"language_label": null,
"active": "1"
}
}
currently these are the structs i've been able to generate for above json
struct user : Decodable {
let status: String
let message: String
let data: userData
}
struct userData : Decodable {
let user_id: Int
let first_name: String
let last_name: String
let other_name: String
let fullname: String
let alias: String
let about: String
let sex_id: Int
let sex: String
let birth_date: String
let relation_status_id: Int
let relation_status: String
let relation_user_id: Int
let relation_user_first_name: String
let relation_user_last_name: String
let relation_user_fullname: String
let location: String
let contact: String
let profile_pic: Int
let profile_folder_name: String
let profile_pic_filename: String
let cover_pic: Int
let cover_folder_name: String
let cover_pic_filename: String
let followers_list: Int
let following_list: Int
let mutual_list: Int
let request_counter: Int
let dream_destination_list: Int
let hidden_content_list: Int
let email: String
let username: String
let password: String
let salt: String
let social_media_auth: String
let social_media_id: Int
let date_created: String
let last_seen: String
let is_suspended: String
let is_notified: String
let approve_token: String
let device_token: String
let language_id: Int
let language_machine_name: String
let language_label: String
let active: Int
}
This is the decoding code i've written to try and parse this
//send request to server
guard let loginUrl = URL(string: "https://xxxxxx.com/api/index.php/Auth/login") else {return}
//request url
var request = URLRequest(url: loginUrl)
// method to pass data
request.httpMethod = "POST"
let body = "username=\(usernameVar)&password=\(passwordVar)"
request.httpBody = body.data(using: String.Encoding.utf8)
//launch session
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
//let json = try JSONSerialization.jsonObject(with: data, options: [])
//print(json)
let userDetails = try JSONDecoder().decode(user.self, from: data)
for details in userDetails {
print(details.message)
}
}catch{
print(error)
}
}
task.resume()
This is the error
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
You should change your error message to a more helpful error message Lol.
You haven't accounted for optional values in your json. Remember there is a difference between String and String? there are several values in your json payload that return nil. In your swift struct you need to mark those fields as Optionals.
You also have mismatched Types. All of your returned types are String or String? not Int. Those are just numbers returned as String?. There are also Dictionary types nested in this payload as well. The followers_list is a dictionary so you need to make a new struct with those properties, called List and in your UserData struct, set the type of those "lists" to [String:List]
Also when you're confused about what kind of error is being thrown while decoding, (and most of the CocoaTouch Framework) those are NSErrors. You can print the value of the error to the console and it'll tell you exactly what error is exiting the scope. IE:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [__lldb_expr_20.user.(CodingKeys in _992B1F7B4C3E0BAC48AEA280B9410D72).data, __lldb_expr_20.userData.,
debugDescription: "Expected String value but found null instead.", underlyingError: nil))
The above error message tells you that CodingKey...userData was set to expect a String but there was null (nil).
Heres what your current struct should look like:
struct User : Decodable {
let status: String
let message: String
let data: userData
}
struct List: Decodable {
var id: String?
var datetime: String?
}
struct UserData : Decodable {
let user_id: String
let first_name: String
let last_name: String
let other_name: String?
let fullname: String
let alias: String?
let about: String
let sex_id: String
let sex: String
let birth_date: String
let relation_status_id: String?
let relation_status: String?
let relation_user_id: String?
let relation_user_first_name: String?
let relation_user_last_name: String?
let relation_user_fullname: String?
let location: String?
let contact: String?
let profile_pic: String
let profile_folder_name: String
let profile_pic_filename: String
let cover_pic: String
let cover_folder_name: String
let cover_pic_filename: String
let followers_list: [String:List]
let following_list: [String:List]
let mutual_list: [String:List]
let request_counter: String
let dream_destination_list: List?
let hidden_content_list: List?
let email: String
let username: String?
let password: String?
let salt: String
let social_media_auth: String?
let social_media_id: String?
let date_created: String
let last_seen: String
let is_suspended: String?
let is_notified: String?
let approve_token: String
let device_token: String?
let language_id: String?
let language_machine_name: String?
let language_label: String?
let active: String
}
Loose the snake case and use CodingKeys to follow Swift Naming Conventions or use a public struct with the values you care about(You don't have to model the entire JSON payload) with a private struct that models the JSON and set the values with a custom init(from decoder: Decoder) using this answer.

Resources