How to use Decodable in Swift? - ios

I am using a free dates API in my project. I am using Decodable to parse the JSON data.
Here I created my struct:-
struct jsonStruct: Decodable {
var message: Bool?
var data: [dateData]
}
struct dateData: Decodable {
var quarter: Int?
var day: String?
var month: String?
}
This is my code to use the decoder:-
let jsonUrlString = "https://api.lrs.org/random-date-generator?lim_quarters=40&source=api-docs"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, reponse, err) in
guard let data = data else { return }
print(data)
do {
let jsonData = try JSONDecoder().decode([dateData].self, from: data)
print(jsonData)
}
catch let jsonerr {
print("error serrializing error",jsonerr)
}
}.resume()
But I am getting an error in my code. It goes in the catch block only and I am getting this error in my console:-
error serrializing error typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
I don't understand what I am doing wrong in my code.
API Data:-
{
messages: false,
data: {
2018-01-02: {
quarter: 1,
day: "2",
month: "1",
db: "2018-01-02",
long: "Tuesday, January 2nd, 2018",
unix: 1514876400
},

struct Job: Decodable {
var title: String
var salary: Float
init(title: String, salary: Float) {
self.title = title
self.salary = salary
}
enum CodingKeys: String, CodingKey {
case title, salary
}
}
struct Person: Decodable {
var job: Job
var firstName: String
var lastName: String
var age: Int
init(job: Job, firstName: String, lastName: String, age: Int) {
self.job = job
self.firstName = firstName
self.lastName = lastName
self.age = age
}
enum CodingKeys: String, CodingKey {
case job = "job_information", firstName = "firstname", lastName =
"lastname", age
}
}
let rawData = """
{
"job_information": {
"title": "iOS Developer",
"salary": 5000
},
"firstname": "John",
"lastname": "Doe",
"age": 20
}
""".data(using: .utf8)!
let person = try JSONDecoder().decode(Person.self, from: rawData)
print(person.firstName) // John
print(person.lastName) // Doe
print(person.job.title) // iOS Developer

You need
struct Root: Codable {
let messages: Bool
let data: [String: Datum]
}
struct Datum: Codable {
let quarter: Int
let day, month, db, long: String
let unix: Int
}
let jsonData = try JSONDecoder().decode(Root.self, from: data)
print(jsonData.data.values)
As the root of the json is a dictionary not an array , also data is a dictionary
jsonData.data.forEach {
if $0 == " 2018-01-02" {
print($1.month)
}
}

Related

How to get value from Optional(Optional(<__NSSingleObjectArrayI >(25)))

I am trying to get the value of "price" key which is "25"
I am getting this response Json From Backend
{
"errorCode": 0,
"message": "Request successfully served.",
"data": {
"games": {
"TWELVEBYTWENTYFOUR": {
"jackpot_amount": "KES 40,000.00",
"draw_date": "2021-05-21 10:59:45",
"extra": {
"jackpotAmount": 40000,
"unitCostJson": [
{
"currency": "KES",
"price": 25
}
]
},
}
},
"currentTime": {
"date": "2021-05-20 22:28:18.738038"
}
}
}
This is my code so far :
fetchData { (dict, error) in
let playerLoginInfo = dataDict["data"] as? NSDictionary
let playerGameInfo = playerLoginInfo?.value(forKey: "games") as? NSDictionary
if let TWELVEBYTWENTYFOUR = playerGameInfo?.value(forKey: "TWELVEBYTWENTYFOUR") as? NSDictionary {
let extra = TWELVEBYTWENTYFOUR.value(forKey: "extra") as? NSDictionary
let unitCostJson = extra?.value(forKey: "unitCostJson") as? NSArray
print("price")
print(unitCostJson?.value(forKey: "price") as? Any)
}
}
I get this is console :
Optional(Optional(<__NSSingleObjectArrayI 0x600001f091d0>(
25
)
))
I have seen this question How can I access values within Optional NSSingleObjectArrayI? but I couldn't figure out a solution
Edit:
I have now used Codeable to get data:
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: Games
let currentTime: CurrentTime
}
struct Games: Codable {
let game_code: String
let datetime: String
let estimated_jackpot: String
let guaranteed_jackpot: String
let jackpot_title: String
let jackpot_amount: String
let draw_date: String
let extra: Extra
let next_draw_date: String
let active: String
}
struct Extra: Codable {
let currentDrawNumber: Int
let currentDrawFreezeDate: String
let currentDrawStopTime: String
let jackpotAmount: Int
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
let timezone_type: Int
let timezone: String
}
I'm trying to get value from price now with this code
do{
let resp:Resp = try JSONDecoder().decode(Resp.self , from:data);
let data = resp.data
let games = data.games
let extra = games.extra
let unitCostJson = extra.unitCostJson
print(unitCostJson[0].price)
}
catch{
GlobalFunctions.shared.callOnMainThread {
self.showAlert(Message: "Something went wrong. Please retry.")
}
}
It is going into catch
How should I get the data inside on the unitCostJson now??
I butchered your struct and removed any irrelevant properties (compared to the json), if you want to add them back then you need to use an CodingKey enum
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: [String:Games]
let currentTime: CurrentTime
}
struct Games: Codable {
let extra: Extra
}
struct Extra: Codable {
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
}
Now you can access the unitCost like this
let unitCost = resp.data.games["TWELVEBYTWENTYFOUR"]?.extra.unitCostJson

Parse Json array without keys swift 4

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

How to store an array of objects in Firestore

I can't find online how to store an array of objects so that the key "line_items" presents numbers for each menuItem with values for each menuItem corresponding to its own number. In other words, I need the numbers to come after line_items rather than the nested key so that each individual MenuItem object can be quickly referenced. I found online how to make it so each key has an array of values, but I need line_items to have an array of MenuItem objects. The following code crashes:
public func uploadTransactionData(_ menuItems: [MenuItem], balanceId: String, subTotal: Int, completion: #escaping (() -> ())) {
guard let userId = Auth.auth().currentUser?.uid else { completion(); return }
let utilitiesManager = UtilitiesManager()
let timestamp = utilitiesManager.timestamp()
let params: [String: Any] = ["date": "\(timestamp)",
"balance_id": "\(balanceId)",
"subtotal": "\(subTotal)",
"user_id": "\(userId)",
"line_items": menuItems
]
Firestore.firestore().document("transaction_history/\(timestamp)").setData(params)
{ err in
if let e = err {
print("$-- error creating user \(e)")
completion()
} else {
completion()
}
}
}
Here's the MenuItem model:
struct MenuItem {
let itemId: String
let name: String
var modifiers: [String]?
var photoName: String?
var photoUrl: String?
var quantity: Int
var price: Int
var sizeAddOnPrice: Int
var toppingsAddOnPrice: Int
let description: String
var size: String
let category: String
init(itemId: String, name: String, modifiers: [String]?, photoName: String?, photoUrl: String?, quantity: Int, price: Int, sizeAddOnPrice: Int, toppingsAddOnPrice: Int, description: String, size: String, category: String) {
self.itemId = itemId
self.name = name
self.modifiers = modifiers
self.photoName = photoName
self.photoUrl = photoUrl
self.quantity = quantity
self.price = price
self.sizeAddOnPrice = sizeAddOnPrice
self.toppingsAddOnPrice = toppingsAddOnPrice
self.description = description
self.size = size
self.category = category
}
Problem:
Your app is crashing because you are trying to save user defined object MenuItem to Firestore. Firestore doesn't allow it. Firestore only supports this datatypes.
Solution:
You can convert your custom object MenuItem to Firestore supported datatypes.
You can do this by making following changes to your code.
Make MenuItem confirm to Codable protocol.
struct MenuItem: Codable {
// Your code as it is.
}
Make following changes to your uploadTransactionData() function:
public func uploadTransactionData(_ menuItems: [MenuItem], balanceId: String, subTotal: Int, completion: #escaping (() -> ())) {
let userId = Auth.auth().currentUser?.uid else { completion(); return }
let utilitiesManager = UtilitiesManager()
let timestamp = utilitiesManager.timestamp()
var list_menuItem = [Any]()
for item in menuItems {
do {
let jsonData = try JSONEncoder().encode(item)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
list_menuItem.append(jsonObject)
}
catch {
// handle error
}
}
let params: [String: Any] = ["date": "\(timestamp)",
"balance_id": "\(balanceId)",
"subtotal": "\(subTotal)",
"user_id": "\(userId)",
"line_items": list_menuItem
]
Firestore.firestore().document("transaction_history/\(timestamp)").setData(params)
{ err in
if let e = err {
print("$-- error creating user \(e)")
completion()
} else {
completion()
}
}
}
This is because Firestore doesn't know how to save the value: menuItems
you can map it like this: "objectExample": [
"a": 5,
"b": [
"nested": "foo"
]
]
or:
.setData([
"name": "Frank",
"favorites": [ "food": "Pizza", "color": "Blue", "subject": "recess" ],
"age": 12
])

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

Decoding/parsing specific JSON with Swift

I am having a lot of trouble trying to decode this JSON with Swift 4.
{
"Items": [
{
"id": 1525680450507,
"animal": "bee",
"type": "insect",
"diet": [
"a",
"b",
"c"
]
}
],
"Count": 1,
"ScannedCount": 5
}
Here's where I try to decode
let decoder = JSONDecoder()
let data = try decoder.decode([Animal].self, from: data)
I have created a struct like this
struct Animal: Codable {
var id: Int
var animal: String
var type: String
var diet: [String]
}
let decoder = JSONDecoder()
let data = try decoder.decode(ItemsResponse.self, from: data)
This doesn't work. I get an error that says
"Expected to decode Array<\Any> but found a dictionary instead."
So I thought maybe I needed something like this
struct ItemsResponse: Codable {
var Items: [Animal]
var Count: Int
var ScannedCount: Int
}
But this doesn't work either. Now I get
"Expected to decode Array<\Any> but found a string/data instead."
How do I make a struct that will decode this JSON?
let data = try decoder.decode([Animal].self, from: data)
[Animal].self is not correct you can use it like this :
struct DataJson: Codable {
let items: [Item]
let count, scannedCount: Int
enum CodingKeys: String, CodingKey {
case items = "Items"
case count = "Count"
case scannedCount = "ScannedCount"
}
}
struct Item: Codable {
let id: Int
let animal, type: String
let diet: [String]
}
// MARK: Convenience initializers
extension DataJson {
init(data: Data) throws {
self = try JSONDecoder().decode(DataJson.self, from: data)
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Item {
init(data: Data) throws {
self = try JSONDecoder().decode(Item.self, from: data)
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
Try this:
import Foundation
let json = """
{
"Items": [
{
"id": 1525680450507,
"animal": "bee",
"type": "insect",
"diet": [
"a",
"b",
"c"
]
}
],
"Count": 1,
"ScannedCount": 5
}
"""
struct Animal: Codable {
var id: Int
var animal: String
var type: String
var diet: [String]
}
struct ItemsResponse: Codable {
var Items: [Animal]
var Count: Int
var ScannedCount: Int
}
let data = try! JSONDecoder().decode(ItemsResponse.self, from: json.data(using: .utf8)!)
Of course, you should properly handle the possible failures (i.e. don't do try!, and don't force-unwrapp the json.data()! part)
But the code above works and hopefully answers your question.

Resources