How To Handle json null values in Swift - json

I am playing With JSON for last two days and facing alot of curious problems and thanks to stack overflow it helps me. This is JSON featured key has two types of String values.
"featured":"1"
or
"featured": null,
I tried a lot to handle this but failed
Step 1:
if dict.objectForKey("featured") as? String != nil {
featured = dict.objectForKey("featured") as? String
}
Step 2:
let null = NSNull()
if dict.objectForKey("featured") as? String != null {
featured = dict.objectForKey("featured") as? String
}
Step 3:
if dict.objectForKey("featured") as? String != "" {
featured = dict.objectForKey("featured") as? String
}
but unfortunately can't found solution, you answer will be appreciated.

Try This
func nullToNil(value : AnyObject?) -> AnyObject? {
if value is NSNull {
return nil
} else {
return value
}
}
object.feature = nullToNil(dict["feature"])
Here, you can use this method, which will convert null value to nil and wont' cause crash in your app.
You can also use as?
object.feature = dict["feature"] as? NSNumber
Thanks.

Here is a working code, type cast operator(as?) will do the trick here. Null will not be typecasted into String, so the execution will go to failure block.
if let featured = dict["featured"] as? String {
print("Success")
}
else {
print("Failure")
}

Try this!
if let demoQuestion = dict.objectForKey("featured"){
let getValue: String = demoQuestion as! String
}
else {
print("JSON is returning nil")
}

Optional chaining with if let or its counterpart guard let is the way to go. All three steps combined (missing, wrong type - NSNull too, empty string):
guard let featured = dict.objectForKey("featured") as? String where !value.isEmpty else {
print("featured has wrong value")
}
// do what you need to do with featured
If you want to know more about optional chaining check out documentation

Hi you can use below function to remove null to empty string and prevent crashes
func removeNullFromDict (dict : NSMutableDictionary) -> NSMutableDictionary
{
let dic = dict;
for (key, value) in dict {
let val : NSObject = value as! NSObject;
if(val.isEqual(NSNull()))
{
dic.setValue("", forKey: (key as? String)!)
}
else
{
dic.setValue(value, forKey: key as! String)
}
}
return dic;
}
and before giving dict to any method call function in below way
let newdict = self.removeNullFromDict(dict: dict);

i did a static func to convert value from json to optional String.
class Tools{
static func setOptionalStr(value : Any?) -> String?{
guard let string = value as! String?, !string.isEmpty else {
return nil
}
return value as? String
}
}
In my controller
let urlStats: String? = Tools.setOptionalStr(value: json["UrlStats"])
i'm open to your feedback

Related

Swift returning NULL in guard statement

I have been working on this bug for about 8 hours now.
Into the function mapJsonToRest I am passing in a JSON object (I have verified it is working inside the function up until the guard let rest statement) and a String. From what I understand the String is trivial, currently "id" is being passed into the function.
From here things gets wonky. This statement is returning out of the function ( I am guessing because it is null.)
guard let rest = object[restKey] as? [[String: AnyObject]] else { return mappedRest }
However I have no idea why it would be doing this. It seems that restKey is null which is causing the else to return. I would appreciate any help because my poor head doesn't have much hair left to pull out.
If it helps, the tutorial I have been following was done in Swift 3 and I am writing this in Swift 5.
import Foundation
class RestDataParser {
static func mapJsonToRest(object: [String: AnyObject], restKey: String) -> [Restaurant] {
var mappedRest: [Restaurant] = []
//This is the humdinger right here
guard let rest = object[restKey] as? [[String: AnyObject]] else { return mappedRest }
for resting in rest {
guard let id = resting["id"] as? String,
let name = resting["name"] as? String,
let rating = resting["rating"] as? String,
let location = resting["address1"] as? String,
let imageURL = resting["image_url"] as? String else { continue }
print("By George, I think we've got it.")
let restClass = Restaurant(id: id, name: name, rating: rating, location: location, imageURL: imageURL)
mappedRest.append(restClass)
}
return mappedRest
}
}

filtering JSON data in swift

I am trying to parse my JSON data and append only those objects into array which meets specified condition. At the moment I have commented out code which fetch all objects from the API and add them into array. However, I would like to limit it so that it only appends objects with "wger.de" value for "license_author" key.
However I am getting error on line:
if eachExercise["license_author"] == "wger.de"
Binary operator '==' cannot be applied to operands of type 'Any?' and 'String'.
However I still wants to keep it as Any object because I would like to fetch both strings and integers data from my API.
This is the code for my parseData() function:
func parseData() {
fetchedExercise = []
let urlPath = "https://wger.de/api/v2/exercise/?format=json&language=2&status=2"
let url = URL(string: urlPath)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let exercises = fetchedData["results"] as? [[String: Any]] {
// WORKING CODE
/*
for eachExercise in exercises
{
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
self.fetchedExercise.append(Exercise(name: name, description: description))
}
*/
// TESTING
for eachExercise in exercises {
if eachExercise["license_author"] == "wger.de" {
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
let id = eachExercise["id"] as! Int
self.fetchedExercise.append(Exercise(name: name, description: description))
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
catch {
print("Error while parsing data.")
}
}
}
task.resume()
}
Use the where clause and optional downcast Any to String
for eachExercise in exercises where eachExercise["license_author"] as? String == "wger.de" { ...
You need cast it to String.
if eachExercise["license_author"] as? String == "wger.de" {
}

SWIFT3 JSON NSNull Issue

I have the following swift3 code. The JSON can return a NSNull value for the $0[2] value.
struct Player3 {
let name : String
var score : String
let avatar : String
}
class HistoricLeagueVC: UITableViewController {
var players = [Player3]()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://str8red.com/jsonoverallleaderboard/1025/"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("there was an error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[String]]
self.players = parsedData.map { Player3(name: $0[0], score: $0[1], avatar: $0[2]) }
print(self.players.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error as NSError {
print(error)
}
}
}.resume()
}
I have tried to convert it to an empty string without success. I have also tried to set a default string such as "https://str8red.com/static/no_profile_picture.png" which ideally is what I would like to do.
The error in the terminal states Could not cast value of type 'NSNull' (0xed1c78) to 'NSString' (0x57b6b8).
Any help would be appreciated.
Do not cast to [[String]] then:
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[Any]]
self.players = parsedData.map { Player3(
name: ($0[0] as? String) ?? "",
score: ($0[1] as? String) ?? "",
avatar: $0[2] as? String, // keeping `nil` here
)}
You should probably even as! [[Any]] do in a safer manner (using as?) but the above will work.
Your code will crash brutally if anything goes wrong with your input data. Ask your boss if he or she is Ok with that. I wouldn't. Here's how you will crash: If data == nil (but it shouldn't be if error == nil). You catch the problem that data cannot be parsed without crashing. You crash if data doesn't parse to an array, you crash if the elements of the array are not all arrays, you crash if the elements inside the inner arrays are not all Strings, and you crash if any of the inner arrays contain two or fewer strings. I'd say that is pretty unacceptable.
My strategy would be: If the response cannot be parsed or is not an array of arrays, then ignore it, leaving self.players unchanged. If there is an array of arrays, reset self.players, then add a player for each array that contains at least three items, of which the first two must be strings.
guard data != nil else { return }
guard let parsed = try? NSJSONSerialization(with: data!) else { return }
guard let arrays = parsed as? [[Any]] else { return }
self.players.removeAll()
for array in arrays {
guard array.count >= 3,
let name = array [0] as? String,
let score = array [1] as? String else { continue }
let avatar = array [2] as? String ?? ""
self.players.append(Player3 (name: name, score: score, avatar: avatar))
}
It's a good idea to move something like this into a separate method. Add whatever error handling or logging you feel is appropriate.

Playground Execution error: Signal SIGABRT while parsing JSON string

I'm really not sure why the JSON parsing causes a SIGABRT error.
class Bug {
enum State {
case open
case closed
}
let state: State
let timestamp: Date
let comment: String
init(state: State, timestamp: Date, comment: String) {
self.state = state
self.timestamp = timestamp
self.comment = comment
}
init(jsonString: String) throws {
let dict = convertToDictionary(from: jsonString)
I think this is what is causing the error but I couldn't figure out why:
self.state = dict["state"] as! Bug.State
self.comment = dict["comment"] as! String
self.timestamp = dict["timestamp"] as! Date
}
}
JSON string to dictionary:
func convertToDictionary(from text: String) -> [String: Any] {
guard let data = text.data(using: .utf8) else { return [:] }
let anyResult: Any? = try? JSONSerialization.jsonObject(with: data, options: [])
return anyResult as? [String: Any] ?? [:]
}
enum TimeRange {
case pastDay
case pastWeek
case pastMonth
}
Error image:
This line appears to be a problem:
self.state = dict["state"] as! Bug.State
Bug.State is a custom type of enum. But the value in dict["state"] is a String. by using as! you are telling the compiler that you know that will be a Bug.State at runtime, but when the system looks while the app is running it finds out that it is a String which is not a Bug.State so it throws an exception.
Similarly on the line that sets the timestamp you are trying to convert what is probably a string into a date using straight type casting. You are going to have to use an NSDateFormatter to extract the date from the string to covert that value into a String.

how to check for JSON nil in Swift in a convenience initializer

I have inherited a Swift project from another developer. We are adding an imageUrl that can now be a part of an item which
is returned from a JSON api. We are returning the using Alamofire.
One issue is that we can have a null value for our instore_image_url_200w which
becomes the item's imageUrl. I guess I have two questions. It would seem like I'd like to have imageUrl be an optional string. Is this correct? If yes, what
is the proper way to handle the nil value during initialization? I'd like to be checking for nil on display for item.imageUrl
var items = [Item]()
Alamofire.request(URL(string: "https://www.example.com/api/v1/metro_areas/1")!, method: .get).validate().responseJSON { (response) -> Void in
guard let value = response.result.value as? [String: Any],
let rows = value["items"] as? [[String: Any]] else {
print("Malformed data received from fetchAllRooms service")
return
}
for row in rows {
let item = Item(data: row)
if (item.imageUrl is String){
print("here is item.imageUrl: \(item.imageUrl!)")
}
items.append(item)
}
}
and the model:
class Item {
var name: String
var imageUrl: String?
init(name: String, imageUrl: String) {
self.name = name
self.imageUrl = imageUrl
}
convenience init(data: [String:Any]) {
let name = data["header"] as? String
let imageUrl = data["instore_image_url_200w"] as! String?
self.init(name: name!,
imageUrl: imageUrl)
}
}
Yes, the imageUrl would be an optional string since it might, or might not, exist in the JSON data. One point - you currently have your model initializer code for the imageUrl as:
let imageUrl = data["instore_image_url_200w"] as! String?
Personally, I think it would be cleaner to have it as:
let imageUrl = data["instore_image_url_200w"] as? String
As for your second question, if you have the initialization as above, the nil value would be automatically handled during initialization since the imageUrl value is optional and so it would either be set to nil (on a nil value) or have an actual string value.
Of course, when you use imageUrl you'd always have to do a check to see if it is nil or has a value, but you already mentioned that you'd be doing that on display.