swift3 optional value issue - json

I have a function to get JSON and put the value into a struct (Job). It prints out the value without optional for the var. But it prints out the struct var with optional. Please kindly help to solve this issue.
var newJob = Job()
var joblist:[Job] = []
func parseJSON(json:Any){
if let okJSON = json as? [Any]{
for item in okJSON {
let infoDictionary = item as! [String:String]
if let activityid = infoDictionary["ActivityID"]
{
newJob.ActivityID=activityid
print(activityid)
print(newJob.ActivityID)
}
if let companyname = infoDictionary["CompanyName"] {newJob.CompanyName=companyname}
if let quantity = infoDictionary["Quantity"] {newJob.Quantity=quantity}
if let coupontitle = infoDictionary["Title"] {newJob.CouponTitle=coupontitle}
if let couponterms = infoDictionary["Terms"] {newJob.CouponTerms=couponterms}
if let expirdate = infoDictionary["ExpirDate"] {newJob.ExpirDate=expirdate}
if let contactperson = infoDictionary["ContactPerson"] {newJob.ContactPerson=contactperson}
if let tel = infoDictionary["TEL"] {newJob.TEL=tel}
joblist.append(newJob)
}
print(joblist)
}
}
Here with the print result:
3
Optional("3")
2
Optional("2")
1
Optional("1")
[cateringhk.Job(ActivityID: Optional("3"), CompanyName: Optional("ABC\351\233集\351\233集\345\351\233集\345\345\234團"), Quantity: Optional("5"), CouponTitle: Optional("$30現金卷"), CouponTerms: Optional("消費滿$100可以使用\r\n台灯固定环E27灯头 \r\n黑色白色固定扣 \r\n台灯灯罩床头灯具固定环配件 \r\n[交易快照]"), ExpirDate: Optional("2017-11-24"), ContactPerson: Optional("陳先生"), TEL: Optional("96855000")), cateringhk.Job(ActivityID: Optional("2"), CompanyName: Optional("皇上皇點心集團"), Quantity: Optional("31"), CouponTitle: Optional("$30現金卷"), CouponTerms: Optional("消費滿$100可以使用"), ExpirDate: Optional("2017-11-24"), ContactPerson: Optional("陳先生"), TEL: Optional("96855000")), cateringhk.Job(ActivityID: Optional("1"), CompanyName: Optional("八樂園酒樓"), Quantity: Optional("22"), CouponTitle: Optional("$20消費券"), CouponTerms: Optional("每1帳單只可以使用一用一\345\274張消費券"), ExpirDate: Optional("2017-11-24"), ContactPerson: Optional("陳小姐"), TEL: Optional("94567821"))]

This behavior is normal if the properties in the struct are also declared as optionals. In this case the unwrapping with optional binding has actually no effect.
To avoid that declare the property as non-optional and assign a default value for example
struct Job {
var activityID = ""
...
}
newJob.activityID = infoDictionary["ActivityID"] ?? ""
But assigning an empty string twice looks cumbersome. I'd add an initializer to the struct to take a dictionary, declare the properties as constants and handle the default value in the init method.
struct Job {
let activityID : String
...
init(dict: [String:String]) {
activityID = dict["ActivityID"] ?? ""
...
}
}
Note:
Please conform to the naming convention that variable names start with a lowercase letter

Related

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

Iterate over JSON and stored pointers in Swift

After receiving a JSON payload from a web request, I need to parse out the data into variables in an NSObject subclass.
My variables are declared in the object:
var name:String?
var email:String?
var aboutMe:String?
Then I start parsing through all the possible data the JSON may return:
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
This is becoming rather long as we have a lot of data.
I was wanting to shorted it by using a Dictionary containing the JSON key and the variable like this:
let varMap:[String:Any?] = [
"Name": self.name,
"Email": self.email,
"AboutMe": self.aboutMe
]
Then iterating over them like this:
for (key, var variable) in varMap
{
if let string = json[key]
{
variable = string
}
}
The problem here is that the Dictionary copies the value from the variable and not a pointer to it, so setting it to a new value has no effect on the variables of the overall object.
Is there a way to achieve what I am trying? If not, what other pattern would be a better way to do this instead of having tons of if statements?
For JSON parsing, you can simply use Codable types.
Let's assume your JSON looks like,
{
"name": "Alex",
"email": "alex#gmail.com",
"about_Me": "My name is Alex"
}
Models:
class Root: Decodable {
let name: String?
let email: String?
let aboutMe: String?
enum CodingKeys: String, CodingKey {
case name, email
case aboutMe = "about_Me"
}
}
Parse the JSON data like so,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response) //use response here...
} catch {
print(error)
}
You can use updateValue function for that, it finds the property and changes it.
if let oldValue = varMap.updateValue(self.name, forKey: "Name") {
print("The old value of \(oldValue) was replaced with a new one.")
}
So you for iteration is;
for (key, var variable) in varMap
{
varMap.updateValue(string, forKey: key )
//if let string = json[key]
//{
// variable = string
//}
}
After you update the dictionary you can call that part;
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
I believe that part in a function, if its not, you can refactor it.
https://developer.apple.com/documentation/swift/dictionary/3127179-updatevalue

Parsing JSON in Swift

I want to parse JSON object that receive from server. there is my code for parsing JSON and create object.
class Transaction {
var id: String!
var amount: String!
var balance: String!
var detail: String!
var serial: String!
var time : String!
var type: String!
init(id: String, amount: String, balance: String, detail:String, serial: String, time: String, type: String ) {
self.id = id
self.amount = amount
self.balance = balance
self.detail = detail
self.serial = serial
self.time = time
self.type = type
}
func CreateTransactionObject(json: [String:Any]) -> Transaction? {
guard let id = json["id"] as? String,
let amount = json["amount"] as? String,
let balance = json["balance"] as? String,
let detail = json["detail"] as? String,
let serial = json["serial"] as? String,
let time = json["time"] as? String,
let type = json["type"] as? String
else {
return nil
}
let object = Transaction(id: id, amount: amount, balance: balance, detail: detail, serial: serial, time: time, type: type)
return object
}
this work fine when guard statement don't return nil.
for example when one of the parameters is null guard statement return nil and object can't create.
how can parse JSON that if any object don't receive from server or get null ?
I recommend to use a dedicated init method.
Declare the properties in two ways:
If an empty string or 0 can represent no value declare the property as non-optional and use the nil coalescing operator (like the first 5 properties).
If no value must be handled separately declare the property as standard optional (like time and type)
class Transaction {
var id: String
var amount: String
var balance: Int
var detail: String
let serial: String
var time : String?
var type: String?
init(json: [String:Any]) {
self.id = json["id"] as? String ?? ""
self.amount = json["amount"] as? String ?? ""
self.balance = json["balance"] as? Int ?? 0
self.detail = json["detail"] as? String ?? ""
self.serial = json["serial"] as? String ?? ""
self.time = json["time"] as? String
self.type = json["type"] as? String
}
}
It's also a good habit to declare properties which are not going to change their value as constants with let (like serial). The initialization works the same way.
If the object is still usable if one of its properties is nil, declare the properties nil and use if let statements for optional unwrapping instead of the guard let. This way, the properties that don't exist will be initialized to nil, instead of the whole object being initialized to nil. This way you don't even need a designated initializer.
class Transaction {
var id: String?
var amount: String?
var balance: String?
var detail: String?
var serial: String?
var time : String?
var type: String?
func CreateTransactionObject(json: [String:Any]) -> Transaction {
let transaction = Transaction()
transaction.id = json["id"] as? String
transaction.amount = json["amount"] as? String
transaction.balance = json["balance"] as? String
transaction.detail = json["detail"] as? String
transaction.serial = json["serial"] as? String
transaction.time = json["time"] as? String
transaction.type = json["type"] as? String
return transaction
}
}

Store a complex object in iOS?

I have a complex class object as follows:
class Ride {
var distance:String?
var price:Double?
var Legs:[Leg]?
var Routes:[Route]?
}
class Leg {
var id:int?
var value:string?
}
class Route {
var id:int?
var value:string?
}
What I want is store these as JSON or whatever with values and read and write to it when I needed. My approach was after create Ride object with data serialize to JSON and write to ridesjson.json file and then when I want to read from that JSON I want to serialize to Ride object. Can I do this or is there any good way to do this?
In Swift 3, the standard approach to serializing an object to a file is to make it adopt NSCoding (it has to be an NSObject for this to work) and implement encode(with:) and init(coder:). Then you can archive to a Data and save that (and reverse the procedure to read it).
class Person: NSObject, NSCoding {
var firstName : String
var lastName : String
override var description : String {
return self.firstName + " " + self.lastName
}
init(firstName:String, lastName:String) {
self.firstName = firstName
self.lastName = lastName
super.init()
}
func encode(with coder: NSCoder) {
coder.encode(self.lastName, forKey: "last")
coder.encode(self.firstName, forKey: "first")
}
required init(coder: NSCoder) {
self.lastName = coder.decodeObject(forKey:"last") as! String
self.firstName = coder.decodeObject(forKey:"first") as! String
super.init()
}
}
Here's an example of archiving it:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let moi = Person(firstName: "Matt", lastName: "Neuburg")
let moidata = NSKeyedArchiver.archivedData(withRootObject: moi)
let moifile = docsurl.appendingPathComponent("moi.txt")
try moidata.write(to: moifile, options: .atomic)
Here we unarchive it:
let fm = FileManager.default
let docsurl = try fm.url(for:.documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: false)
let moifile = docsurl.appendingPathComponent("moi.txt")
let persondata = try Data(contentsOf: moifile)
let person = NSKeyedUnarchiver.unarchiveObject(with: persondata) as! Person
print(person) // "Matt Neuburg"
In Swift 4, however, it may be easier to use the Codable protocol so as to make your object serializable as a property list or as JSON more or less automatically. Codable works on any type, including a struct. This is all it takes:
struct Person : Codable {
let firstName : String
let lastName : String
}
Here's how to archive a Person:
let p = Person(firstName: "Matt", lastName: "Neuburg")
let penc = PropertyListEncoder()
let d = try! penc.encode(p)
That's a Data object and you can write it directly to disk as in the previous example. Unarchiving is just as simple. Assume d is the Data you've read from disk:
let p = try! PropertyListDecoder().decode(Person.self, from: d)

Swift Failable Initializer with SwiftyJSON

I’m trying to initialise a simple data model object with some JSON from SwiftyJSON. I’d like the initialiser to fail and return nil if any of the required JSON values aren’t present. Here’s my code:
class ProductCategory: NSObject {
let id: String
let sortOrder: Int
let name: String
let imageURL: String
let ranges: [String]
init?(json: JSON) {
if let jsonID = json["id"].string,
jsonSortOrder = json["sortOrder"].int,
jsonName = json["name"].string,
jsonImageURL = json["imageURL"].string {
id = jsonID
sortOrder = jsonSortOrder
name = jsonName
imageURL = jsonImageURL
ranges = json["ranges"].arrayValue.map { $0.string! }
} else {
return nil
}
}
}
I’d expect this to work. In the event that we didn’t hit all those json values, simply return nil and bail out. However, I get an error on the return nil, stating:
All stored properties of a class instance must be initialized before
returning nil from an initializer.
I’m confused: isn’t the point of a failable initializer that I can bail out without setting it up if something goes wrong? The object returned would be nil, why would there be any value in setting up its properties?
So here’s what I ended up doing – Greg was right but I ended up switching to a struct as a result:
struct ProductCategory {
let id: String
let sortOrder: Int
let name: String
let imageURL: String
let ranges: [String]
init?(json: JSON) {
guard let jsonID = json["id"].string,
let jsonSortOrder = json["sortOrder"].int,
let jsonName = json["name"].string,
let jsonImageURL = json["image"].string else {
return nil
}
self.id = jsonID
self.sortOrder = jsonSortOrder
self.name = jsonName
self.imageURL = jsonImageURL
self.ranges = json["ranges"].arrayValue.map { $0.string! }
}
}
Failable Initializers for Classes:
"For classes, however, a failable initializer can trigger an initialization failure only after all stored properties introduced by that class have been set to an initial value and any initializer delegation has taken place."
So
init?(json: JSON) {
self.id = json["id"].string
self.sortOrder = json["sortOrder"].int
...
if ... { return nil }
}