Swift - How to init an array in struct? - swift

I'm not sure how to init the array in Struct. I'm not able to fetch data from array, meanwhile I was manage to get the result from object (platform.status).
Am I init it wrongly ?
Any ideas ?
Here is Network Request :
func fetchServicePlatform(token: String, _key: String) {
let selectedUrl = URL(string: "\(mainUrl)/get_service")
let parameters: [String: String] = ["_key": _key]
var serviceList = [ServiceList]()
URLSession.shared.dataTask(with: setupURLRequest(selectedURL: selectedUrl!, parameters: parameters, token: token, email: "test#gmail.com")) { (data, response, error) in
if let err = error {
print("Failed to fetch API: \(err.localizedDescription)")
}
guard let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else { return }
let platform = Platform(json: json)
if platform.status == "success" {
self.serviceList = platform.service_list
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
} catch let jsonErr {
print("Failed to fetch service platform: ", jsonErr.localizedDescription)
}
}.resume()
}
Here is JSON :
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
Here is Struct :
struct Platform {
let service_list: [ServiceList]
let status: String
init(json: [String: Any]) {
service_list = [ServiceList(json: json["service_list"] as? [String: Any] ?? [:])]
status = json["status"] as? String ?? ""
}
}
struct ServiceList {
let title: String
let description: String
let image: String
init(json: [String: Any]) {
title = json["title"] as? String ?? ""
description = json["description"] as? String ?? ""
image = json["image"] as? String ?? ""
}
}

In your data json["service_list"] is an array of dictionaries,
You Can try out.
struct Platform {
var service_list: [ServiceList] = []
var status: String
init(json: [String: Any]) {
if let jsonArray = json["service_list"] as? [[String: Any]] {
for service in jsonArray {
service_list.append(ServiceList(json: service))
}
}
else{
service_list = []
}
status = json["status"] as? String ?? ""
}
}

init an array in the struct
struct MyData {
var dataArray:[Any] = []
var latitude: Float
var longitude: Float
}

You have to unwrap the dictionary as an array of dictionaries and then loop through it with a map or flatmap, where you use the value $0 as the value
guard let serviceList = (json["service_list"] as? [[String: Any]])?.flatmap({ServiceList(withDictionary: $0)})

Go with the below approach, this way it is simpler
Create your structure with Codable
struct Platform: Codable {
let service_list: [ServiceList]
let status: String
enum CodingKeys: String, CodingKey {
case service_list, status
}
}
struct ServiceList: Codable {
let title: String
let description: String
let image: String
enum CodingKeys: String, CodingKey {
case title, description, image
}
}
Your json data object
let json = """
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
""".data(using: .utf8)!
Use the JSONDecoder to map the json object to structure
let decoder = JSONDecoder()
let platform = try! decoder.decode(Platform.self, from: json)
if platform.status == "pass"{
for service in platform.service_list{
print(service.title)
}
}

Related

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

convert Codable to Json (Dictionary) Swift

How can I convert Codable to Json Dictionay its a nested struct
struct one
struct User: Codable {
var name = "siddhant"
var age = 12
var topInt = ["1","2","3"]
var moreDetails = MoreDetails()
}
struct Two
struct MoreDetails: Codable {
var image = "ImageUrl"
}
I need out put as a Json when I convert this to Json it just convert me the struct one not the 2nd struct
do {
let sid = try JSONEncoder().encode(users)
let dict = try JSONSerialization.jsonObject(with: sid, options: []) as? [String: Any]
print(dict)
}
catch {
print(error)
}
current output which is wrong:
(["name": siddhant, "topInt": <__NSArrayI 0x2831a1c20>(1,2,3),
"moreDetails": {
image = ImageUrl;
}, "age": 12])
I want output like
{
"name": "siddhant",
"topInt": ["1", "2", "3"],
"moreDetails": {
"image": "ImageUrl"
},
"age": 12
}
You can create an extension on Encodable so that you can use it for all elements that conform to Encodable protocol
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] }
}
}
In your case if the model is user of type User
guard let userDict = user.dictionary else { return }
what I did now is
let users = User()
let staticJson = ["name": users.name,
"age": users.age,
"topInt": users.topInt,
"moreDetails": ["image": users.moreDetails.image]] as? [String: Any]
You need to specify what is your desired result. Following your code:
struct User: Codable {
var name = "siddhant"
var age = 12
var topInt = ["1","2","3"]
var moreDetails = MoreDetails()
var dictionaryJson: [String : Any] {
let encoder = JSONEncoder()
return (try? JSONSerialization.jsonObject(with: encoder.encode(self), options: .allowFragments)) as? [String: Any] ?? [:]
}
}
struct MoreDetails: Codable {
var image = "ImageUrl"
}
do {
let users = User()
let jsonData = try JSONEncoder().encode(users)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
print("\n")
print(users.dictionaryJson)
print("\n")
print("Getting the var from dictionary")
print(users.dictionaryJson["moreDetails"])
} catch {
print(error)
}
The output:
{"age":12,"moreDetails":{"image":"ImageUrl"},"name":"siddhant","topInt":["1","2","3"]}
["topInt": <__NSArrayI 0x6000008e8150>(
1,
2,
3
)
, "name": siddhant, "age": 12, "moreDetails": {
image = ImageUrl;
}]
Getting the var from dictionary
Optional({
image = ImageUrl;
})

Casting JSON array to a struct model swift

I am parsing a json array of object into a model which works. In this object, there is an array to a value and I created another model to handle that array but when ever I pass this object, the internal array returns nil after casting as the model class. Any help is appreciated
JSON Sample
[
{
"code": "AF",
"code3": "AFG",
"dial_code": "+93",
"name": "Afghanistan",
"capital": "Kabul",
"region": "Asia",
"subregion": "Southern Asia",
"states": [
{
"code": "BDS",
"name": "Badakhshān",
"subdivision": null
},
{
"code": "BGL",
"name": "Baghlān",
"subdivision": null
}
]
}
}
]
MODEL
public struct LocaleInfo {
public var locale: Locale?
public var id: String? {
return locale?.identifier
}
public var country: String
public var code: String
// public var phoneCode: String
public var states: [LocalStateInfo]
public var flag: UIImage? {
return UIImage(named: "Countries.bundle/Images/\(code.uppercased())", in: Bundle.main, compatibleWith: nil)
}
public var currencyCode: String? {
return locale?.currencyCode
}
public var currencySymbol: String? {
return locale?.currencySymbol
}
public var currencyName: String? {
guard let currencyCode = currencyCode else { return nil }
return locale?.localizedString(forCurrencyCode: currencyCode)
}
init(country: String, code: String/*, phoneCode: String*/, states: [LocalStateInfo]) {
self.country = country
self.code = code
self.states = states
self.locale = Locale.availableIdentifiers.map { Locale(identifier: $0) }.first(where: { $0.regionCode == code })
}
}
public struct LocalStateInfo {
public var code: String
public var name: String
public var subdivision: String
}
Passing the JSON Body
func getInfo(completionHandler: #escaping (FetchResults) -> ()) {
let bundle = Bundle(for: LocalePickerViewController.self)
let path = "Countries.bundle/Data/CountryCodes"
guard let jsonPath = bundle.path(forResource: path, ofType: "json"),
let jsonData = try? Data(contentsOf: URL(fileURLWithPath: jsonPath)) else {
let error: (title: String?, message: String?) = (title: "ContryCodes Error", message: "No ContryCodes Bundle Access")
return completionHandler(FetchResults.error(error: error))
}
if let jsonObjects = (try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments)) as? Array<Any> {
var result: [LocaleInfo] = []
for jsonObject in jsonObjects {
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
guard let country = countryObj["name"] as? String,
let code = countryObj["code"] as? String/*,
let phoneCode = countryObj["dial_code"] as? String*/ else {
fatalError("Broken here")
continue
}
log("countryObj state \(countryObj["states"] as? [LocalStateInfo])", .fuck)
log("countryObj \(countryObj)", .fuck)
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
let new = LocaleInfo(country: country, code: code/*, phoneCode: phoneCode*/, states: states)
result.append(new)
}
return completionHandler(FetchResults.success(response: result))
}
let error: (title: String?, message: String?) = (title: "JSON Error", message: "Couldn't parse json to Info")
return completionHandler(FetchResults.error(error: error))
}
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
is presumably the line that isn't working for you. But countryObj is just a dictionary straight from JSON:
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
Why would casting it to an array of LocalStateInfo work at at all? It's an array of dictionaries, and you need to parse each one out individually.
You've said using Codable would alter the "entire scope" of the library, I don't understand how this is the case. You can implement codable (or even just Decodable) without affecting any other file.

keyNotFound(CodingKeys(stringValue: "coord", intValue: nil)

I am building a small swift weather app using the openweatherAPI and I am running into some issues trying to parse the JSON. I have used the following function to parse the get and parse the json.
Below is my weather data struct:
struct WeatherData: Codable {
let coord: Coord
let weather: [Weather]
let base: String
let main: Main
let visibility: Int
let wind: Wind
let clouds: Clouds
let dt: Int
let sys: Sys
let id: Int
let name: String
let cod: Int
}
struct Clouds: Codable {
let all: Int
}
struct Coord: Codable {
let lon, lat: Double
}
struct Main: Codable {
let temp: Double
let pressure, humidity: Int
let tempMin, tempMax: Double
enum CodingKeys: String, CodingKey {
case temp, pressure, humidity
case tempMin = "temp_min"
case tempMax = "temp_max"
}
}
struct Sys: Codable {
let type, id: Int
let message: Double
let country: String
let sunrise, sunset: Int
}
struct Weather: Codable {
let id: Int
let main, description, icon: String
}
struct Wind: Codable {
let speed: Double
let deg: Int
}
private func getWeatherData(url: String, parameters: [String : String]) {
let JsonURLString:[String: Any] = ["url": WEATHER_URL, "parameters": parameters]
print(JsonURLString)
let urlString = JsonURLString["url"] as? String
guard let url = URL(string: urlString!) else { return }
URLSession.shared.dataTask(with: url) { ( data, response, err ) in
DispatchQueue.main.sync {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
The exact error I am getting is the following:
longitude = -0.1337, latitude = 51.50998
["parameters": ["lat": "51.50998", "long": "-0.1337", "appid": "xxxxxxxxxxxxxxxxxx"], "url": "https://api.openweathermap.org/data/2.5/weather"]
keyNotFound(CodingKeys(stringValue: "coord", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"coord\", intValue: nil) (\"coord\").", underlyingError: nil))
I have looked at the following example and don't see how this would apply. Any help would be appreciated.
Icon is not appearing. Here is my model:
import UIKit
class WeatherDataModel {
//Declare your model variables here
var temperature: Int = 0
var condition: Int = 0
var city: String = ""
var weatherIconName = ""
var description: String = ""
//This method turns a condition code into the name of the weather condition image
func updateWeatherIcon(condition: Int) -> String {
switch (condition) {
case 0...300 :
return "tstorm1"
case 301...500 :
return "light_rain"
case 501...600 :
return "shower3"
case 601...700 :
return "snow4"
case 701...771 :
return "fog"
case 772...799 :
return "tstorm3"
case 800 :
return "sunny"
case 801...804 :
return "cloudy2"
case 900...903, 905...1000 :
return "tstorm3"
case 903 :
return "snow5"
case 904 :
return "sunny"
default :
return "dunno"
}
}
}
I have added my own icons. I have added this in the do catch block.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.weatherData.weatherIconName = WeatherDataModel.updateWeatherIcon(self.weatherData.condition)
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
The error I am getting this error now:
Instance member 'updateWeatherIcon' cannot be used on type 'WeatherDataModel'; did you mean to use a value of this type instead?
You are creating only the openweathermap URL but you ignore the parameters.
Use something like this for example URLComponents and URLQueryItem to build the URL query properly
private func getWeatherData(parameters: [String : String]) {
guard let lat = parameters["lat"],
let long = parameters["long"],
let appID = parameters["appid"] else { print("Invalid parameters"); return }
var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")!
let queryItems = [URLQueryItem(name: "lat", value: lat),
URLQueryItem(name: "lon", value: long),
URLQueryItem(name: "appid", value: appID)]
urlComponents.queryItems = queryItems
guard let url = urlComponents.url else { return }
URLSession.shared.dataTask(with: url) { ( data, response, err ) in
DispatchQueue.main.async { // never, never, never sync !!
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let city = try decoder.decode(WeatherData.self, from: data)
print(city)
self.weatherData.description = city.weather[0].description
self.weatherData.temperature = Int(city.main.temp - 273)
self.weatherData.city = city.name
self.weatherData.condition = city.weather[0].id
self.updateUIWeatherData()
} catch {
print(error)
self.cityLabel.text = "Connection issues"
}
}
}.resume()
}
and pass only
["lat": "51.50998", "long": "-0.1337", "appid": "xxxxxxxxxxxxxxxxxx"]
as parameters.

Decode an array inside JSON data only using Swift 4 Codable

I'm using Swift 4 Codable and I'm receiving this JSON from my web service:
{
"status": "success",
"data": {
"time": "00:02:00",
"employees": [
{
"id": 001,
"name": "foo"
},
{
"id": 002,
"name": "bar"
}
]
}
}
I want to decode only employees array into employee objects (the time property will only be saved once), but nothing works.
I read a lot of materials about Swift 4 Codable but don't get how can I decode this array.
EDIT: My employee class:
import Foundation
struct Employee: Codable {
var id: Int
var time: Date
enum CodingKeys: String, CodingKey {
case id = "id"
case time = "time"
}
}
The request:
Alamofire.SessionManager.default.request(Router.syncUsers)
.validate(contentType: ["application/json"])
.responseJSON { response in
if response.response?.statusCode == 200 {
guard let jsonDict = response as? Dictionary<String, Any>,
let feedPage = Employee(from: jsonDict as! Decoder) else {
return
}
guard let response = response.result.value as? [String: Any] else {
return
}
guard let data = response["data"] as? [String: Any] else {
return
}
guard let users = data["employees"] else {
return
}
guard let usersData = try? JSONSerialization.data(withJSONObject: users, options: .prettyPrinted) else {
return
}
guard let decoded = try? JSONSerialization.jsonObject(with: usersData, options: []) else {
return
}
let decoder = JSONDecoder()
guard let employee = try? decoder.decode(Employee.self, from: usersData) else {
print("errorMessage")
return
}
} else {
print("errorMessage")
}
}
When using Codable you cannot decode inner data without decoding the outer.
But it's pretty simple, for example you can omit all CodingKeys.
struct Root : Decodable {
let status : String
let data : EmployeeData
}
struct EmployeeData : Decodable {
let time : String
let employees : [Employee]
}
struct Employee: Decodable {
let id: Int
let name: String
}
let jsonString = """
{
"status": "success",
"data": {
"time": "00:02:00",
"employees": [
{"id": 1, "name": "foo"},
{"id": 2, "name": "bar"}
]
}
}
"""
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
for employee in result.data.employees {
print(employee.name, employee.id)
}
} catch { print(error) }
You can solve this kind of JSON parsing problem very easily with quicktype. Just paste in your JSON on the left and you'll get types and serialization/deserialization code on the right, for a variety of languages. Here are the types it produces for your JSON:
struct Employees: Codable {
let status: String
let data: EmployeesData
}
struct EmployeesData: Codable {
let time: String
let employees: [Employee]
}
struct Employee: Codable {
let id: Int
let name: String
}