parsing nested Array using codable in swift 4 - ios

I am getting many random issues. Mostly like some structure is not decodable not able to understand how to define structure.
Please find the code snipped
var JSON = """
{"variants":{"variant_groups":[{"group_id":"1","name":"Crust","variations":[{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]},{"group_id":"2","name":"Size","variations":[{"name":"Small","price":0,"default":1,"id":"10","inStock":1,"isVeg":0},{"name":"Medium","price":100,"default":0,"id":"11","inStock":1,"isVeg":1},{"name":":Large","price":200,"default":0,"id":"12","inStock":1,"isVeg":0}]},{"group_id":"3","name":"Sauce","variations":[{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]}],"exclude_list":[[{"group_id":"1","variation_id":"3"},{"group_id":"2","variation_id":"10"}],[{"group_id":"2","variation_id":"10"},{"group_id":"3","variation_id":"22"}]]}}
""".data(using: .utf8)
/*
not sure is this the right way to define Root
*/
struct Root : Codable {
let variants : varientStruct
let exclude_list : exclude_list
}
struct exclude_list : Codable{
let variation_id : String
let group_id : String
}
struct varientStruct: Codable {
let variant_groups = [variant_groups_struct]
}
struct variant_groups_struct : Codable {
let group_id : String
let name :String
let variations: [variationsStruct]
}
struct variationsStruct :Codable {
let name : String
let price : Int
let selected: Int
let id : String
let inStock: Bool
enum CodingKeys : String, CodingKey {
case name
case price
case selected = "default"
case id
case inStock
}
}
}
do {
let data = Data(person.utf8)
let result = try JSONDecoder().decode(Root.self, from: JSON)
print(result)
} catch {
print(error)
}

First of all and once again, please conform to the naming convention:
struct and class names start with a uppercase letter.
Variable and function names start with a lowercase letter.
All variable and struct / class names are camelCased rather than snake_cased.
Second of all, JSON is very easy to read. There are only two collection types (array [] and dictionary {}) and four value types.
Format the JSON string to be able to recognize the structure more conveniently
let jsonString = """
{"variants":{"variant_groups":[{"group_id":"1","name":"Crust","variations":
[{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},
{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},
{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]
},{"group_id":"2","name":"Size","variations":
[{"name":"Small","price":0,"default":1,"id":"10","inStock":1,"isVeg":0},
{"name":"Medium","price":100,"default":0,"id":"11","inStock":1,"isVeg":1},
{"name":":Large","price":200,"default":0,"id":"12","inStock":1,"isVeg":0}]
},{"group_id":"3","name":"Sauce","variations":
[{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},
{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},
{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]
}],
"exclude_list":[[{"group_id":"1","variation_id":"3"}, {"group_id":"2","variation_id":"10"}],
[{"group_id":"2","variation_id":"10"},{"group_id":"3","variation_id":"22"}]]
}
}
"""
Then build the structs according to the JSON structure step by step
struct Root : Decodable {
let variants : Variant
}
struct Variant : Decodable {
private enum CodingKeys : String, CodingKey {
case groups = "variant_groups"
case excludeList = "exclude_list"
}
let groups : [VariantGroup]
let excludeList : [[ExcludeList]]
}
struct VariantGroup : Decodable {
private enum CodingKeys : String, CodingKey {
case groupID = "group_id"
case name, variations
}
let groupID : String
let name : String
let variations : [Variation]
}
struct Variation : Decodable {
let name : String
let price : Int
let `default` : Int
let id : String
let inStock : Int
}
struct ExcludeList : Decodable {
private enum CodingKeys : String, CodingKey {
case groupID = "group_id"
case variationID = "variation_id"
}
let groupID : String
let variationID : String
}
Then decode the stuff
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch { print(error) }

Related

Retrieve Array from Firebase Firestore using REST API on iOS

I'm using REST APIs to retrieve data from my Firestore DB. I'm forced to use REST API instead of the Firebase SDK since App Clip don't allow to use the latter.
The JSON file is the following: JSON File
And, as text:
{
"name": "projects/myProject/databases/(default)/documents/Brand/rxnBLnp736gqjFBNLxxx",
"fields": {
"descrizione": {
"stringValue": "My project Brand Demo"
},
"descrizione_en": {
"stringValue": "My project Brand Demo"
},
"listaRefsLinea": {
"arrayValue": {
"values": [
{
"referenceValue": "projects/myProject/databases/(default)/documents/Linea/aeeDNuY9xEvRvyM5cxxx"
}
]
}
},
"data_consumption": {
"stringValue": "7xpISf0XxRnfrnUkNxxx"
},
"url_logo": {
"stringValue": "gs://myproject.appspot.com/FCMImages/app-demo-catalogue.png"
},
"web_url": {
"stringValue": "www.mybrand.it"
},
"nome_brand": {
"stringValue": "My project Demo"
}
},
"createTime": "2021-05-19T10:34:51.828685Z",
"updateTime": "2022-05-24T14:03:16.121296Z"
}
And I'm decoding it as follows:
import Foundation
struct BrandResponse : Codable {
let brands : [Brand_Struct]
private enum CodingKeys : String, CodingKey {
case brands = "documents"
}
}
struct StringValue : Codable {
let value : String
private enum CodingKeys : String, CodingKey {
case value = "stringValue"
}
}
struct Brand_Struct : Codable {
let url_logo : String
let web_url : String
let nome_brand : String
let descrizione : String
let listaRefsLinea : [String]
let descrizione_en : String
let data_consumption : String
private enum BrandKeys : String, CodingKey {
case fields
case listaRefsLinea
}
private enum FieldKeys : String, CodingKey {
case url_logo
case web_url
case nome_brand
case descrizione
case listaRefsLinea
case descrizione_en
case data_consumption
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: BrandKeys.self)
let fieldContainer = try container.nestedContainer(keyedBy: FieldKeys.self, forKey: .fields)
//listaRefsLinea = try containerListaRefsLinea_2.decode(ArrayValue.self, forKey: .values).referenceValue
nome_brand = try fieldContainer.decode(StringValue.self, forKey: .nome_brand).value
web_url = try fieldContainer.decode(StringValue.self, forKey: .web_url).value
url_logo = try fieldContainer.decode(StringValue.self, forKey: .url_logo).value
descrizione = try fieldContainer.decode(StringValue.self, forKey: .descrizione).value
descrizione_en = try fieldContainer.decode(StringValue.self, forKey: .descrizione_en).value
data_consumption = try fieldContainer.decode(StringValue.self, forKey: .data_consumption).value
listaRefsLinea = [""] // <-- How to read this??
}
}
My issue is that I'm not being able to read the array inside the field "listaRefsLinea". Any idea on how to achieve that? Also I'm afraid that part of the troubles come from the fact that that's a Document Reference variable and as such does not conform to the Codable protocol.
Well. listaRefsLinea is a custom object just like your StringValue
So add these structs:
// MARK: - ListaRefsLinea
struct ListaRefsLinea: Codable {
let arrayValue: ArrayValue
}
// MARK: - ArrayValue
struct ArrayValue: Codable {
let values: [Value]
}
// MARK: - Value
struct Value: Codable {
let referenceValue: String
}
and in your custom init decode it to this struct, go down the tree until you get the array and map that to a [String]:
listaRefsLinea = try fieldContainer.decode(ListaRefsLinea.self, forKey: .listaRefsLinea)
.arrayValue.values.map{ $0.referenceValue }

Is it possible to declare generics inside Decodable model

Here is my model class
struct ErrorData: Decodable {
let code : Int
let message : String
let data : [ErrorDataFields]
}
i want to have ErrorDataFields to be array and object like
struct ErrorData: Decodable {
let code : Int
let message : String
let data : [ErrorDataFields]
}
AND
struct ErrorData: Decodable {
let code : Int
let message : String
let data : ErrorDataFields
}
We can use a generic type with Decodable. Only thing is the generic type should also conform to Decodable.
struct ErrorData <T : Decodable> : Decodable {
let code : Int
let message : String
let data : T
}

Swift 4 Codable : Common struct for all model

Here i am getting API response of all of my api.
{
"success" : true,
"message" : "",
"data" : {
/multipal data parameter/
}
}
And here is my codable model
struct Login: Codable {
let success: Bool
let message: String
let data: Data
struct Data: Codable {
}
}
How can i create common Sturct for success and message parameter.
You can make the root struct representing the network response generic, this will allow you to keep the success and message parts common between all specialised responses.
struct NetworkResponse<ResponseData:Codable>: Codable {
let success: Bool
let message: String
let data: ResponseData
}
You shouldn't create custom types with the same name as built in types, since that will lead to confusion, especially for other people reading your code, so I renamed your custom Data type to ResponseData.
For instance you can create a LoginResponse model and decode it like below. You can do the same for other responses from the same API.
let loginResponse = """
{
"success" : true,
"message" : "",
"data" : {
"username":"test",
"token":"whatever"
}
}
"""
struct LoginResponse: Codable {
let username: String
let token: String
}
do {
print(try JSONDecoder().decode(NetworkResponse<LoginResponse>.self, from: Data(loginResponse.utf8)))
} catch {
print(error)
}
Common structure :
I have created something like that
struct statusModel<T:Codable>: Codable {
let message : String
let resultData : [T]?
let status : Int
enum CodingKeys: String, CodingKey {
case message = "message"
case resultData = "resultData"
case status = "status"
}
}
Regular model (resultData)
struct modelInitialize : Codable {
let profileimgurl : String?
let projecturl : String?
enum CodingKeys: String, CodingKey {
case profileimgurl = "profileimgurl"
case projecturl = "projecturl"
}
}
You can set like as below
do {
guard let reponseData = responseData.value else {return} //Your webservice response in Data
guard let finalModel = try?JSONDecoder().decode(statusModel<modelInitialize>.self, from: reponseData) else {return}
}

How to organise JSON Structs in an array

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

raw value for enum case must be a literal error with string

I'd like to use the same struct to fetch from different APIs, for that I need to be able to change the enum string depending on what I need to fetch as follows:
static var menuSelection: String = ""
if ... {
menuSelection = "1"
} else if ... {
menuSelection = "2"
} else {
menuSelection = "3"
}
struct Order : Decodable {
private enum CodingKeys : String, CodingKey { case raw = "RAW" }
let raw : MenuRAW
}
struct MenuRAW : Decodable {
private enum CodingKeys : String, CodingKey { case menu = "\(menuSelection)" } // <---- raw value for enum case must be a literal
let menu : MenuReference
}
struct MenuReference : Decodable {
private enum CodingKeys : String, CodingKey {
case usd = "USD"
case eur = "EUR"
case gbp = "GBP"
case cny = "CNY"
case rub = "RUB"
}
let usd : MenuUSD?
let eur : MenuEUR?
let gbp : MenuGBP?
let cny : MenuCNY?
let rub : MenuRUB?
}
But I get a raw value for enum case must be a literal as "\(menuSelection)"doesn't seem to be a literal. What is my solution here?
You can do it in a different way, create enum with parameter
enum CodingKeys {
case menu(menuSelection: String)
var stringValue: String? {
switch self {
case let .menu(menuSelection):
return menuSelection
default:
return nil
}
}
}
and now you can use stringValue instead of rawValue like this
CodingKeys.menu(menuSelection: "You parameter").stringValue

Resources