Parse JSON with swiftyJSON and more - json

Hi I've tried to parse my JSON but I couldn't get my data from it,
(I used SwiftyJSON)
how can I parse this ugly JSON?
//Mark: parser functions:
private func parseProvincesResult(provincesJSON: JSON, completion: #escaping(_ :ProvincesV2) -> Void) {
print(provincesJSON)
let errorCode: Int = provincesJSON["ErrorCode"].intValue
let errorDescriptions: String = provincesJSON["ErrorString"].stringValue
let newMacKey: String = provincesJSON["NewMacKey"].stringValue
let newPinKey: String = provincesJSON["NewPinKey"].stringValue
let version: Int = provincesJSON["Ver"].intValue
var provinceList: [ProvinceListResult] = []
for i in provincesJSON["ProvinceListResult"].arrayValue {
let id: Int = i["Id"].intValue
let name: String = i["Name"].stringValue
let proList = ProvinceListResult(id: id, name: name)
provinceList.append(proList)
}
let model = ProvincesV2(errorCode: errorCode, errorDescriptions: errorDescriptions, newMacKey: newMacKey, newPinKey: newPinKey, version: version, provinceList: provinceList)
completion(model)
}
and my JSON is:
{"ErrorCode":"8",
"ErrorString":"عملیات با موفقیت انجام شد.",
"NewMacKey":"vph+eLFgxa6LVq90QfsNUA==",
"NewPinKey":"evJiM9W6S9RWEClR6csxEQ==",
"Ver":201,
"ProvinceListResult":[{"Id":1,"Name":"آذربايجان شرقي"},
{"Id":2,"Name":"آذربايجان غربي"},
{"Id":3,"Name":"اردبيل"},
{"Id":4,"Name":"اصفهان"},
{"Id":5,"Name":"البرز"},
{"Id":6,"Name":"ايلام"},
{"Id":7,"Name":"بوشهر"},
{"Id":8,"Name":"تهران"},
{"Id":9,"Name":"چهارمحال و بختياري"},
{"Id":10,"Name":"خراسان جنوبي"},{"Id":11,"Name":"خراسان رضوي"},{"Id":12,"Name":"خراسان شمالي"},{"Id":13,"Name":"خوزستان"},{"Id":14,"Name":"زنجان"},{"Id":15,"Name":"سمنان"},{"Id":16,"Name":"سيستان و بلوچستان"},{"Id":17,"Name":"فارس"},{"Id":18,"Name":"قزوين"},{"Id":19,"Name":"قم"},{"Id":20,"Name":"کردستان"},{"Id":21,"Name":"کرمان"},{"Id":22,"Name":"کرمانشاه"},{"Id":23,"Name":"کهکيلويه و بويراحمد"},{"Id":24,"Name":"گلستان"},{"Id":25,"Name":"گيلان"},{"Id":26,"Name":"لرستان"},{"Id":27,"Name":"مازندران"},{"Id":28,"Name":"مرکزي"},{"Id":29,"Name":"هرمزگان"},{"Id":30,"Name":"همدان"},{"Id":31,"Name":"يزد"},{"Id":44,"Name":"کیش"}]}
how can I parse It?
tnx

Using Codable, you could do this:
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let errorCode, errorString, newMACKey, newPinKey: String
let ver: Int
let provinceListResult: [ProvinceListResult]
enum CodingKeys: String, CodingKey {
case errorCode = "ErrorCode"
case errorString = "ErrorString"
case newMACKey = "NewMacKey"
case newPinKey = "NewPinKey"
case ver = "Ver"
case provinceListResult = "ProvinceListResult"
}
}
// MARK: - ProvinceListResult
struct ProvinceListResult: Codable {
let id: Int
let name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
(generated by https://app.quicktype.io)
And getting a value out might look like:
let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
print(welcome?.provinceListResult[1])
print(welcome?.provinceListResult[1])
If your original data is in String form, it can be converted to Data by doing this:
myStringJSON.data(using: .utf8)!

create two models for parse JSON
first for the province:
public struct ProvinceModel {
var id: Int?
var name: String?
init(json: JSON) {
self.id = json["Id"].int
self.name = json["Name"].string
}
}
with a constructor, you can parse JSON and set data to variables.
and second model:
public struct MyJSONModel {
var errorString: String?
var newMacKey: String?
var newPinKey: String?
var Ver: Int?
var Provinces: [ProvinceModel]?
init(json: JSON) {
// simple parse
self.errorString = json["ErrorCode"].string
self.newMacKey = json["NewMacKey"].string
self.newPinKey = json["NewPinKey"].string
self.Ver = json["Ver"].int
// array
self.Provinces = [] // empty array
if let jsonArray = json["ProvinceListResult"].array {
for json in jsonArray {
let ProvinceObject = ProvinceModel.init(json: json)
self.Provinces?.append(ProvinceObject)
}
}
}
}
for parse province items in the array, you must create a loop and create an object then set in variables
I prefer to use a constructor for parse JSON instead of a custom function.

Related

Swift: How to declare a node type which match any arbitrary JSON?

Following code loads JSON from a file, and expecting it has an xsd type.
struct xsd: Decodable {
var name: String
var address: String
}
do {
if let jsonURL = Bundle.main.url(forResource: "user", withExtension: "json") {
let jsonData = try Data(contentsOf: jsonURL)
let jsonDecoder = JSONDecoder()
let persons = try jsonDecoder.decode(xsd.self, from: jsonData)
print(persons)
let ii = 7
}
} catch {
}
What if xsd would be more dynamic and it would match any arbitrary JSON in the file?
How can I define a type which would represent a JSON attribute-key pairs? It should be a recursive structure.
Here guy define one for HTML. https://theswiftdev.com/result-builders-in-swift/
I would do the same for JSON.
This is a Node for HTML:
public struct Node {
public enum `Type` {
case standard // <name>contents</name>
case comment // <!-- contents -->
case empty // <name>
case group // *group*<p>Lorem ipsum</p>*group*
}
public let type: `Type`
public let name: String?
public let contents: String?
public init(type: `Type` = .standard,
name: String? = nil,
contents: String? = nil) {
self.type = type
self.name = name
self.contents = contents
}
}

Swift 5 Parsing strange json format

I'm trying to parse JSON but keep getting incorrect format error. The JSON I get back from FoodData Central (the USDA's Nutrition API) is as follows:
{
dataType = "Survey (FNDDS)";
description = "Chicken thigh, NS as to cooking method, skin not eaten";
fdcId = 782172;
foodNutrients = (
{
amount = "24.09";
id = 9141826;
nutrient = {
id = 1003;
name = Protein;
number = 203;
rank = 600;
unitName = g;
};
type = FoodNutrient;
},
{
amount = "10.74";
id = "9141827";
nutrient = {
id = 1004;
name = "Total lipid (fat)";
number = 204;
rank = 800;
unitName = g;
};
type = FoodNutrient;
}
);
}
My Structs:
struct Root: Decodable {
let description: String
let foodNutrients: FoodNutrients
}
struct FoodNutrients: Decodable {
// What should go here???
}
From the JSON, it looks like foodNutrients is an array of unnamed objects, each of which has the values amount: String, id: String, and nutrient: Nutrient (which has id, name etc...) However, forgetting the Nutrient object, I can't even parse the amounts.
struct FoodNutrients: Decodable {
let amounts: [String]
}
I don't think its an array of string, but I have no idea what the () in foodNutrients would indicate.
How would I go about parsing this JSON. I'm using Swift 5 and JSONDecoder. To get the JSON I use JSONSerializer, then print out the JSON above.
This is not a JSON. This is a property list in the openStep format.
This is how it can be modelled (use String instead of Int):
struct Root: Decodable {
let description: String
let foodNutrients: [FoodNutrient]
}
struct FoodNutrient: Decodable {
let id: String
let amount: String
let nutrient: Nutrient
}
struct Nutrient: Decodable {
let name: String
let number: String
let rank: String
let unitName: String
}
And then decode it like this:
try PropertyListDecoder().decode(Root.self, from: yourStr)
The () in foodNutrients indicates that it holds an array of objects - in that case FoodNutrient objects. Therefore your root object should look like this:
struct Root: Decodable {
let description: String
let foodNutrients: [FoodNutrient]
}
Now the foodNutrient is except for the nutrient object straightforward:
struct FoodNutrient: Decodable {
let id: Int // <-- in your example it is an integer and in the second object a string, choose the fitting one from the API
let amount: String
let nutrient: Nutrient
}
And the nutrient object should look like this:
struct Nutrient: Decodable {
let name: String
let number: Int
let rank: Int
let unitName: String
}
Using Decodable is a good and easy way to serialize JSON. Hope that helps. Happy coding :)

How to decode this using JSONDecoder and Swift 4 Decodabale class

I have decoded this using JSONSerialization. But for my own knowledge and the maintenance of my code. I would like to know how to decode this.
This is what I have so far:
let urlString = "site deleted" + "/DataSource/Mobile/?Action=MyProfile&uid=" + uid + "&uuid=" + uuid
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let err = error {
print("Handle MyProfileJSON error: ", err)
}
guard let data = data else {return}
do {
// swift 4.2 but we cant use it right now
let profile = try JSONDecoder().decode(RequestResult.self, from: data)
print(profile)
completion(profile)
} catch let err {
print("Handle Decoder Error: ", err)
}
}.resume()
I'm not too worried about the cases but this is what I have so far. I know the case I use is not the convention, that's why I did this with JSONSerialization so I can use camelCase. If you can help me convert it to camelCase too that would be amazing but my focus is to Decode this using Decodable class. Thanks a lot, guys.
And this are my structs:
struct RequestResult: Decodable {
var Action: String?
var Params: [String: String]?
var DATA: [String: String]?
}
struct Params: Decodable {
var Action_get: String?
var uid_get: String?
}
struct DATA: Decodable {
var Id: String?
var UserCode: String?
var HomePhone: String?
var Mobile: String?
var WorkPhone: String?
var Email: String?
var AltEmail: String?
var UnitNo: String?
var StreetNo: String?
var StreetName: String?
var City: String?
var StateProvince: String?
var Country: String?
var ZipPostalCode: String?
}
The structure of the JSON is very clear
The root object RequestResult contains a string and two dictionaries.
The dictionaries are replaced by structs.
The CodingKeys are useful to rename the keys to more meaningful and naming convention conforming names. The left side of an enum case is the struct member name, the right side the original JSON key.
A struct member name must match the dictionary key (or the mapped CodingKey).
The struct names are arbitrary.
All struct members can be declared as constants (let) and as non-optional if the JSON contain always the keys.
struct RequestResult: Decodable {
private enum CodingKeys : String, CodingKey {
case action = "Action", params = "Params", data = "DATA"
}
let action: String
let params: Parameter
let data: UserData
}
The dictionary for key Params will be renamed to Parameter and DATA to UserData
struct Parameter: Decodable {
private enum CodingKeys : String, CodingKey {
case action = "Action_get", uid = "uid_get"
}
let action: String
let get: String
}
struct UserData: Decodable {
private enum CodingKeys : String, CodingKey {
case id = "Id", userCode = "UserCode", homePhone = "HomePhone"
case mobile = "Mobile", workPhone = "WorkPhone", email = "Email"
case altEmail = "AltEmail", unitNo = "UnitNo", streetNo = "StreetNo"
case streetName = "StreetName", city = "City", stateProvince = "StateProvince"
case country = "Country", zipPostalCode = "ZipPostalCode"
}
let id: String, userCode, homePhone, mobile: String
let workPhone, email, altEmail, unitNo: String
let streetNo, streetName, city, stateProvince: String
let country, zipPostalCode: String
}

SWIFT 4, Xcode 9, JSON DECODER

I'm pretty stuck now. I'm attempting to PARS a JSON RETURN for just the year make make and model. It's buried in an array of dictionaries, and the decoder is having a hard time pulling them out. What am I doing wrong?
public struct Page: Decodable {
let Count: Int
let Message: String
let SearchCriteria: String
let Results: [car]}
public struct car: Decodable {
let ModelYear: String
let Make: String
let Model: String
let VIN: String}
let session = URLSession.shared
let components = NSURLComponents()
components.scheme = "https"
components.host = "vpic.nhtsa.dot.gov"
components.path = "/api/vehicles/decodevinvaluesextended/\(VIN)"
components.queryItems = [NSURLQueryItem]() as [URLQueryItem]
let queryItem1 = NSURLQueryItem(name: "Format", value: "json")
components.queryItems!.append(queryItem1 as URLQueryItem)
print(components.url!)
let task = session.dataTask(with: components.url!, completionHandler: {(data, response, error) in
guard let data = data else { return }
do
{
//let Result = try JSONDecoder().decode(Page.self, from: data)
// let PageResult = try JSONDecoder().decode(Page.self, from: data)
let json = try JSONDecoder().decode(Page.self, from: data)
let Results = json.Results;
print(Results)
First of all it's highly recommended to conform to the Swift naming convention that variable names start with a lowercase and structs start with a capital letter
public struct Page: Decodable {
private enum CodingKeys : String, CodingKey {
case count = "Count", message = "Message", searchCriteria = "SearchCriteria", cars = "Results"
}
let count: Int
let message: String
let searchCriteria: String
let cars: [Car]
}
public struct Car: Decodable {
private enum CodingKeys : String, CodingKey {
case modelYear = "ModelYear", make = "Make", model = "Model", VIN
}
let modelYear: String
let make: String
let model: String
let VIN: String
}
The cars array is in the variable result. This code prints all values
let result = try JSONDecoder().decode(Page.self, from: data)
for car in result.cars {
print("Make: \(car.make), model: \(car.model), year: \(car.modelYear), VIN: \(car.VIN)")
}

Initialize Swift class with AnyObject? from NSJSONSerialization

I am using NSJSONSerialization in Swift 1.2 to parse some json that is returned from an API response.
var err: NSError?
let opts = NSJSONReadingOptions.AllowFragments
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData!, options: opts, error: &err)
The parsed json is provided as AnyObject?. I would like to use this optional to initialize a class object which can be used as the model data in an application.
class Alerts {
let type: String
let date: String
let description: String
let expires: String
let message: String
init(json: AnyObject) {
if let
jsonDict = json as? [String: AnyObject],
alertsArray = jsonDict["alerts"] as? [AnyObject],
alertsDict = alertsArray[0] as? [String: AnyObject],
type = alertsDict["type"] as? String,
date = alertsDict["date"] as? String,
description = alertsDict["description"] as? String,
expires = alertsDict["expires"] as? String,
message = alertsDict["message"] as? String
{
self.type = type
self.date = date
self.description = description
self.expires = expires
self.message = message
}
else
{
self.type = "err"
self.date = "err"
self.description = "err"
self.expires = "err"
self.message = "err"
}
}
}
// example of creating a data model from the json
let alerts = Alerts(json: json!)
alerts.type
alerts.date
alerts.description
alerts.expires
alerts.message
Since NSJSONSerialization returns an optional, I have to check for the existence of each value type as I extract the json data. As you can see in the above code, I used the improved optional bindings from Swift 1.2 to clean up the init method. Without using third-party libraries, is there anything else I can do to the class model (enums, structs, type aliases) to make it more readable? Should I use a struct for the model data instead of a class? Would it be possible to create a custom type using an enum or struct to represent a json object?
So without using third party libraries, the if let trees are usually the best practice, which you have shown. To help you later down the road, maybe recreate your object hierarchy in JSON as a Struct model in Swift. Something like:
var json = JSON(JSONData.sharedjson.jsonRaw!)
var mongoIdTest = json["resultset"]["account"]["mongoId"].string
struct Root {
var timestamp: Int?
var resultset = ResultSet()
init() {
self.timestamp = json["timestamp"].int
println(json)
}
}
struct ResultSet {
var alert: String?
var account = Account()
var customer = Customer()
init() {
}
}
struct Account {
var mongoId: String?
init() {
mongoId = json["resultset"]["account"]["mongoId"].string
}
}
struct Locations {
}
struct Customer {
var account: String?
var address: String?
var id: String?
var loginId: String?
var mongoId: String?
var name: String?
var opco = Opco()
init() {
account = json["resultset"]["customer"]["account"].string
address = json["resultset"]["customer"]["address"].string
id = json["resultset"]["customer"]["id"].string
loginId = json["resultset"]["customer"]["loginId"].string
mongoId = json["resultset"]["customer"]["mongoId"].string
name = json["resultset"]["customer"]["name"].string
}
}
struct Opco {
var id: String?
var phone: String?
var cutOffTime: String?
var name: String?
var payerId: String?
init() {
id = json["resultset"]["customer"]["opco"]["id"].string
phone = json["resultset"]["customer"]["opco"]["phone"].string
cutOffTime = json["resultset"]["customer"]["opco"]["cutOffTime"].string
name = json["resultset"]["customer"]["opco"]["name"].string
payerId = json["resultset"]["customer"]["opco"]["payerId"].string
}
}
This way you can still use autocomplete and dot notation to navigate through your hierarchy.
Edit: I have a data structure from an actual project I've worked on added to the answer, hopefully this gives a better idea. Keep in mind that I'm using SwiftyJSON for the JSON() call.
Edit 2:
This is a method I found for getting JSON info into a Swift dictionary without the use of some other library. I'm not sure there is another way to do it that's easier without the use of third party libraries.
var urlToRequest = "https://EXAMPLE.com/api/account.login?username=MY_USERNAME&password=Hunter2"
if let json = NSData(contentsOfURL: NSURL(string: urlToRequest)!) {
// Parse JSON to Dictionary
var error: NSError?
let boardsDictionary = NSJSONSerialization.JSONObjectWithData(json, options: NSJSONReadingOptions.MutableContainers, error: &error) as? Dictionary<String, AnyObject>
fulljson = boardsDictionary
// Display all keys and values
println("Keys in User Data:")
for (key, value) in boardsDictionary! {
println("\(key)-------\(value)")
}
println(fulljson?["resultset"])
}
else {
println("Test JSON nil: No Connection?")
}
That dictionary will be the input for your Structs.