skip to Main Content

I’m currently pulling data via Twitter’s Search API.

Once I receive the data, I am trying to parse and save the data to my TweetDetails class (which is to hold the author’s name, the url of their profile pic, the text of the tweet, the location of the tweet, the tweet id and user id).

In some cases, the Tweets do not have a location (I think it has something to do if they’re retweeted), and the certain dictionary (here being tweetsDict[“place”]) that would otherwise hold that information, instead returns NSNull.

On those occasions, I receive this error

Could not cast value of type ‘NSNull’ (0x1093a1600) to ‘NSDictionary’ (0x1093a0fe8).

Here is my code as I am trying to parse the data and save it to objects in my TweetDetails class

    client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
        if connectionError != nil {
            print("Error: (connectionError)")
        }
        do {
            let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])

            if let tweets = json["statuses"] as? [NSDictionary] {
                for tweetsDict in tweets {
                    let text = tweetsDict["text"] as! String
                    let tweetID = tweetsDict["id_str"] as! String
                    let place = tweetsDict["place"] as! NSDictionary

                    // this line ^^ is where the error occurs
                    let city = place.valueForKey("full_name")
                    let user = tweetsDict["user"] as! NSDictionary
                    let userID = user.valueForKey("id_str")
                    let screenName = user.valueForKey("screen_name")!
                    let avatarImage = user.valueForKey("profile_image_url_https")!

                    let tweet = TweetDetails(authors: screenName as! String, profileImages: avatarImage as! String, tweetTexts: text, tweetLocations: city as! String , tweetIDs: tweetID , userIDs: userID as! String)
                    self.allTweets.append(tweet)
                    self.tweetTableView.reloadData()
                }
            }
        } catch let jsonError as NSError {
            print("json error: (jsonError.localizedDescription)")
        }
    }

I have tried to create some ‘if-let’ statements to provide alternatives for these occasions, but nothing has worked.

Could someone please help me to create a custom alternative around when certain JSON data returns as NSNull. (Even something as simple as turning my “city” variable into the string “Unknown Location” in the cases when the data returns NSNull).

Thanks in advance and please let me know if there is anything else I can add to provide more clarity to this question.

2

Answers


  1. I see a lot of force data type casting in your code witch is very dangerous, especially when parsing JSON. The correct way of dealing with nullability is not to avoid it by trying to do the same thing we did in objective-c but to prepare to receive nil and react accordingly. Your data model should represent the fact that sometime a tweet have no location so some properties of TweetDetails should be marked as optionals.

    The code exemple bellow is completely untested but give you an idea of what your code can look like dealing with nullability.

    client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
        if connectionError != nil {
            print("Error: (connectionError)")
        }
        do {
            let json = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
        if let tweets = json["statuses"] as? [[String: AnyObject]] {
            for tweetsDict in tweets {
                if let text = tweetsDict["text"] as? String,
                tweetID = tweetsDict["id_str"] as? String {
    
                    var city: String?
                    if let place = tweetsDict["place"] as? [String: AnyObject] {
                        city = place.valueForKey("full_name")
                    }
    
                    var userID: String?
                    var screenName: String?
                    var avatarImage: String?
                    if let user = tweetsDict["user"] as? [String: AnyObject] {
                        userID = user["id_str"] as? String
                        screenName = user["screen_name"] as? String
                        avatarImage = user["profile_image_url_https"] as? String
                    }
    
                    let tweet = TweetDetails(authors: screenName, profileImages: avatarImage, tweetTexts: text, tweetLocations: city, tweetIDs: tweetID, userIDs: userID)
                    self.allTweets.append(tweet)
                }
            }
            self.tweetTableView.reloadData()
        }
    } catch let jsonError as NSError {
        print("json error: (jsonError.localizedDescription)")
        }
    }
    
    Login or Signup to reply.
  2. As others have pointed out, you can use optional binding (if let), or, in this case, even easier, just employ optional chaining:

    let city = tweetsDict["place"]?["full_name"] as? String 
    

    This way, city is an optional, i.e. if no value was found because there was no place or full_name entry, it will be nil.

    It would appear, though, that your TweetDetails is force casting city to a String. Does it absolutely require a city? It would be best to change this method so that city was optional and gracefully handle nil values there. Alternatively, you can replace nil values for city to some other string, e.g. with the nil coalescing operator:

    let city = tweetsDict["place"]?["full_name"] as? String ?? "Unknown city"
    

    That returns the city if found, and “Unknown city” if not.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search