How to parse a optional JSON object using JSONJoy? - ios

https://github.com/daltoniam/JSONJoy-Swift
For example :
JSON1 = {
"message": "Sorry! Password does not match.",
"code": "4"
}
JOSN2 = {
"data": {
"id": 21
},
"message": "Signup Successful.",
"code": "1"
},
Here json key “data” is optional. Then how I can handle both response using the same model object??

JSONJoy natively sets not found elements to nil, you just have to declare them optional and then check for nil before using them.
From the docs
This also has automatic optional validation like most Swift JSON libraries.
//some randomly incorrect key. This will work fine and the property
will just be nil.
firstName = decoder[5]["wrongKey"]["MoreWrong"].string
//firstName is nil, but no crashing!
Here is my example that my be illustrative. I have a complex object set where my top level object (UserPrefs) has arrays of secondary objects (SmartNetworkNotification and SmartNotificationTime).
Note that notifications and times are both declared as optional. What I do is check for nil after attempting to parse the secondary object arrays. Without the nil check the attempt to iterate on the parsed list fails since its nil. With the nil check it just moves past it if its empty.
This works for me but isn't deeply tested yet. YMMV! Curious how others are handling it.
struct UserPrefs: JSONJoy {
var notifications: [SmartNetworkNotification]?
var times: [SmartNotificationTime]?
init(_ decoder: JSONDecoder) throws {
// Extract notifications
let notificationsJson = try decoder["notifications"].array
if(notificationsJson != nil){
var collectNotifications = [SmartNetworkNotification]()
for notificationDecoder in notificationsJson! {
do {
try collectNotifications.append(SmartNetworkNotification(notificationDecoder))
} catch let error {
print("Error.. on notifications decoder")
print(error)
}
}
notifications = collectNotifications
}
// Extract time of day settings
let timesJson = try decoder["times"].array
if(timesJson != nil){
var collectTimes = [SmartNotificationTime]()
for timesDecoder in timesJson! {
do {
try collectTimes.append(SmartNotificationTime(timesDecoder))
} catch let error {
print("Error.. on timesJson decoder")
print(error)
}
}
times = collectTimes
}
}

Related

Xcode, The given data was not valid JSON, Can't read the JSON from API properly

Hello I am creating an app with Xcode and I am having the following problem, I created this API (if you enter the link you'll see the JSON data) https://proyecto-idts6.epizy.com/models/getCategorias.php
If you dont want to enter the link here is how this si how the structure of the JSON looks like:
{
"items":[
{
"categorie":"Fruits",
"id_categorie":"1"
},
{
"categorie":"Animals",
"id_categorie":"2"
},
{
"categorie":"Juices",
"id_categorie":"3"
},
{
"categorie":"Vegetables",
"id_categorie":"4"
},
{
"categorie":"Alcohol",
"id_categorie":"5"
},
{
"categorie":"Desserts",
"id_categorie":"6"
}
]
}
The problem I have is that when I try to decode the data from the API it cant't be decoded properly, I am trying to recreate the same code of this youtube video, but with my API: https://www.youtube.com/watch?v=sqo844saoC4
What I want basically is to print the categories and storage each of them in variables (because i'll need to move the variables between screens)
This is how my code looks like:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://proyecto-idts6.epizy.com/models/getCategorias.php"
getData(from: url)
//Here is where i want to storage the variables from the JSON
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
do {
let result = try JSONDecoder().decode([ResultItem].self, from: data)
print(result)
}
catch {
print("failed to convert\(error)")
}
})
task.resume()
}
}
struct Response: Codable {
let items: [ResultItem]
}
struct ResultItem: Codable {
let categorie: String
}
My goal is to have variables for example like this: categorie1=("the category 1 called from the JSON"), categorie2=("the category 2 called from the JSON"), categorie3=("the category 3 called from the JSON"),...
The problem is not in the decoding but in the remote API.
Your endpoint (https://proyecto-idts6.epizy.com/models/getCategorias.php) instead of returning a JSON is returning the following HTML
<html><body><script type="text/javascript" src="/aes.js" ></script><script>function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e="",f=0;f<d.length;f++)e+=(16>d[f]?"0":"")+d[f].toString(16);return e.toLowerCase()}var a=toNumbers("f655ba9d09a112d4968c63579db590b4"),b=toNumbers("98344c2eee86c3994890592585b49f80"),c=toNumbers("f5490e280a5e50f74932909856c3d3a3");document.cookie="__test="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/"; location.href="https://proyecto-idts6.epizy.com/models/getCategorias.php?i=1";</script><noscript>This site requires Javascript to work, please enable Javascript in your browser or use a browser with Javascript support</noscript></body></html>
So you are trying to decode that HTML content, which clearly leads to the error your reported
failed to convertdataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around line 1, column 0." UserInfo={NSDebugDescription=Invalid value around line 1, column 0., NSJSONSerializationErrorIndex=0})))

Successful Firestore Write Not Reflected in Data

I am expecting data for a document field to be updated via a setData (merge) write and it does not despite completing successfully. An updateData works, but I don't understand why setData doesn't work in this instance.
I am performing the following writes to Firestore and the following paths:
setData to firstCollection/uniqueDoc
batched setData to secondCollection/uniqueDoc1 (between 1-5 writes in batch)
transaction updateData to thirdCollection/uniqueDoc
No error prints out, all data and syntax appear correct, and #2 plus #3 writes reflect in the data successfully - #1 is the one that does not.
Any guidance would be appreciated.
Here's my code for #1...
func first() {
//oldData is not empty and is the same type as var updatedData
//oldData = [
// "a": ["name":"aName", "dateAdded": dateValue, "imageUrl": "aImageUrl"]
// "b": ["name":"bName", "dateAdded": dateValue, "imageUrl": "bImageUrl"]
// ]
guard let oldData = oldData else { return }
var updatedData: [String: NSDictionary] = [:]
updatedData = oldData
updatedData["a"] = nil
db.collection(firstCollection).document(uniqueDocName).setData(["data": updatedData],
merge: true) { error in
guard error == nil else {
print("Error:" , error)
return
}
self.runOtherFunc()
}
}

How to handle failure cases of JSON response using Codable?

I have some JSON response, that I take from a server. In success case, it might be like:
{
"success": true,
"data": [
{
/// something here
}
]
}
If all server responses would be successful, it would be really easy to parse that JSON. But we have also failure cases like:
{
"success": false,
"msg": "Your session expired",
"end_session": true
}
That means we need to handle two cases. As you noticed, attributes like success, msg may occur in any response. In order to handle that, I created following struct:
struct RegularResponse<T: Codable>: Codable {
let success: Bool
let msg: String?
let endSession: Bool?
let data: T?
enum CodingKeys: String, CodingKey {
case success, msg, data
case endSession = "end_session"
}
}
It may contain some data if response is successfull or otherwise, it is possible to identify why the error occurred(using success attribute or msg). Parsing process would go like following:
let model = try JSONDecoder().decode(RegularResponse<MyModel>.self, from: data)
if model.success {
// do something with data
} else {
// handle error
}
Everything works fine, but what if following JSON comes as following:
{
"success": true,
"name": "Jon Snow",
"living_place": "Nights Watch",
//some other fields
}
Here, I don't have data attribute. It means, my RegularResponse cannot be parsed. So, the question is how to handle these kind of situations? My idea for solution is simple: always put data in success cases into data field on my API. By doing so, my RegularResponse will always work, no matter what is inside data. But, it requires changes on a server side. Can this be fixed in a client side, not changing a server side? In other words, how to handle above situation in Swift using Codable?
I'm not sure if this is the best solution but if you know that your error response is in that shape, i.e.:
{
"success": false,
"msg": "Some error",
"end_session": "true",
}
then you could make another Codable struct/class that follows this response.
struct ErrorResponse: Codable {
let success: Bool
let msg: String
let end_session: String
}
and then when you are responding to your JSON you could adjust your code to:
if let successResponse = try? JSONDecoder().decode(RegularResponse<MyModel>.self, from: data) {
//handle success
} else if let responseError = try? JSONDecoder().decode(ErrorResponse.self, from data) {
//handle your error
}

Convert any object to boolean in swift?

I am getting a dictionary as JSON response from server.From that dictionary there is a key "error" now i want to get the value of "error" key.I know that error always will be either 0 or 1.So i tried to get it as boolean but it did not work for me.Please suggest how can i convert its value to boolean.
let error=jsonResult.objectForKey("error")
//this does not work for me
if(!error)
{
//proceed ahead
}
Bool does not contain an initializer that can convert Int to Bool. So, to convert Int into Bool, you can create an extension, i.e.
extension Bool
{
init(_ intValue: Int)
{
switch intValue
{
case 0:
self.init(false)
default:
self.init(true)
}
}
}
Now you can get the value of key error using:
if let error = jsonResult["error"] as? Int, Bool(error)
{
// error is true
}
else
{
// error does not exist/ false.
}
Very Simple Way: [Updated Swift 5]
Create a function:
func getBoolFromAny(paramAny: Any)->Bool {
let result = "\(paramAny)"
return result == "1"
}
Use the function:
if let anyData = jsonResult["error"] {
if getBoolFromAny(anyData) {
// error is true
} else {
// error is false
}
}
Also, if you want one line condition, then above function can be reduced like:
if let anyData = jsonResult["error"], getBoolFromAny(anyData) {
// error is true
} else {
// error is false
}
Updated
Considering jsonResult as NSDictionary and error value is Bool. you can check it by following way.
guard let error = jsonResult.object(forKey: "error") as? Bool else{
// error key does not exist / as boolean
return
}
if !error {
// not error
}
else {
// error
}
Your error is neither 0 or 1. It is an optional object. It is nil if there was no key "error" in your JSON, otherwise it's probably an NSNumber.
It is most likely that nil means "no error" and if it's not nil and contains a zero it means "no error" and if it's not nil and contains a one it means "error" and if it's not nil and contains anything else then something is badly wrong with your json.
Try this:
let errorObject = jsonResult ["error"]
if errorObject == nil or errorObject as? NSInteger == 0
When I say "try", I mean "try". You're the responsible developer here. Check if it works as wanted with a dictionary containing no error, with a dictionary containing an error 0 or 1, and a dictionary containing for example error = "This is bad".
Check equality between error and 0 instead:
let error = jsonResult.objectForKey("error")
if error == 0 {
// proceed
}
Another Important point here is, you're using Swift and parsing the repsonse as NSDictionary. I can say that because you're using objectForKey method. Best practice would be to parse it as Swift Dictionary.
It may look like Dictionary<String:AnyObject> or [String:AnyObject]
Then you can check for the error as :
if let error = jsonResult["error"] where error == 0 {
//your code
}
Below code worked for me
if let error = jsonResult["error"] as? Int where Bool(error)
{
// error is true
print("error is true")
}
else
{
// error does not exist/ false.
print("error is false")
}

How can I use swiftyJSON dictionaryValue as a usable string for a UILabel?

I have a makeRequest() method inside a UITableViewController with the following code:
func makeRequest() {
Alamofire.request(.GET, self.foursquareEndpointURL, parameters: [
//"VENUE_ID" : self.foursquareVenueID,
"client_id" : self.foursquareClientID,
"client_secret" : self.foursquareClientSecret,
"v" : "20140806"
])
.responseJSON(options: nil) { (_, _, data, error) -> Void in
if error != nil {
println(error?.localizedDescription)
} else if let data: AnyObject = data {
let jObj = JSON(data)
if let venue = jObj["response"]["venue"].dictionaryValue as [String: JSON]? {
self.responseitems = jObj
println("venue is: \(venue)")
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData() // Update UI
}
}
}
}
also keep in mind that I have a property var responseitems:JSON = []
println("venue is: \(venue)") prints a nice looking response to console, so I know that is working correctly...
I also have a custom UITableViewCell class with a bindData() method with the following code:
func bindData() {
println("VenueDetailHeaderCell data did set")
self.venueDetailTitleLabel.text = self.headerInfo?["name"].stringValue
let labelData = self.headerInfo?["name"].stringValue
println("labelData is: \(labelData)")
}
As you can see, I am attempting to set a UILabel's text to the ["name"].stringValue in the JSON response. However, when I println("labelData is: \(labelData)") I get console output of labelData is: Optional("") which is obviously empty.
Here's a screenshot of what I'm trying to grab
What am I doing wrong here and how can I grab the name of the venue and assign my UILabel to it?
UPDATE:
I tried the following code
let labelData = self.headerInfo?["name"].error
println("labelData is: \(labelData)")
And get a console output of: "Error Domain=SwiftyJSONErrorDomain Code=901 "Array[0] failure, It is not an array" UserInfo=0x7fd6d9f7dc10 {NSLocalizedDescription=Array[0] failure, It is not an array}" If that is of use to anyone. I am really confused here... Any ideas?
The problem is that the headerInfo value is an error JSON object, because you're trying to access a dictionary with an integer index.
Note that var responseitems:JSON = [] does not create an array object. SwiftyJSON has auto-assignment-constructors (I'm new to swift, so not sure what the correct swift terminology is)... see this initialiser in the SwiftyJSON.swift source code:
extension JSON: ArrayLiteralConvertible {
public init(arrayLiteral elements: AnyObject...) {
self.init(elements)
}
}
What this means is that when you do var responseitems:JSON = [] you are not creating an array, you are creating a JSON object that is constructed with an empty array using the above init method. Then when you do self.responseitems = jObj you are re-assigning that responseitems variable to a JSON object with a dictionary in it. Therefore self.responseitems[0] is invalid.
Also note that with SwiftyJSON, there is no such thing as an optional JSON object. I notice in your comment you say that you do var headerInfo:JSON? ... - it's not possible to have an optional JSON.
var headerInfo: JSON = nil
The above is possible - this uses another auto-initialiser that initialises a valid JSON object that represents the JSON null value.
So, how to fix it?
When you assign headerInfo do it like this:
let headerInfo = self.responseitems["response"]["venue"]
And now in bindData you can do:
self.venueDetailTitleLabel.text = self.headerInfo["name"].stringValue
Note that all of the above assumes Swift 1.2 and SwiftyJSON >= 2.2 - also after you've understood the above and corrected the issue, you will probably want to refactor the code a bit to reflect the corrected understanding of the data-model.
The way to parse is the next
element: JSONValue //Something with JSONValue
if let title = element["name"]?.string {
cell.title.text = title
}

Resources