Detect a Null value in NSDictionary
Asked Answered
H

6

41

I have an NSDictionary that's populated from a JSON response from an API server. Sometimes the values for a key in this dictionary are Null

I am trying to take the given value and drop it into the detail text of a table cell for display.

The problem is that when I try to coerce the value into an NSString I get a crash, which I think is because I'm trying to coerce Null into a string.

What's the right way to do this?

What I want to do is something like this:

cell.detailTextLabel.text = sensor.objectForKey( "latestValue" ) as NSString

Here's an example of the Dictionary:

Printing description of sensor:
{
    "created_at" = "2012-10-10T22:19:50.501-07:00";
    desc = "<null>";
    id = 2;
    "latest_value" = "<null>";
    name = "AC Vent Temp";
    "sensor_type" = temp;
    slug = "ac-vent-temp";
    "updated_at" = "2013-11-17T15:34:27.495-07:00";
}

If I just need to wrap all of this in a conditional, that's fine. I just haven't been able to figure out what that conditional is. Back in the Objective-C world I would compare against [NSNull null] but that doesn't seem to be working in Swift.

Hewes answered 3/6, 2014 at 23:24 Comment(1)
NSNull the class exists in Swift, so you could compare to NSNull()Wennerholn
T
88

You can use the as? operator, which returns an optional value (nil if the downcast fails)

if let latestValue = sensor["latestValue"] as? String {
    cell.detailTextLabel.text = latestValue
}

I tested this example in a swift application

let x: AnyObject = NSNull()
if let y = x as? String {
    println("I should never be printed: \(y)")
} else {
    println("Yay")
}

and it correctly prints "Yay", whereas

let x: AnyObject = "hello!"
if let y = x as? String {
    println(y)
} else {
    println("I should never be printed")
}

prints "hello!" as expected.

Tumular answered 3/6, 2014 at 23:29 Comment(8)
Will this work even if his value is actually an instance of NSNull?Wennerholn
@TimGostony unless the as? specification is misleading, yes, it will work, since the cast will fail.Tumular
When using the downcast, I get an error in the Playground about the as? usage: Could not find an overload for 'subscript' that accepts the supplied argumentsWennerholn
@TimGostony I tried it in an app and it appears to work.Tumular
Did you try it fetching the object from a dictionary though?Wennerholn
@TimGostony I haven't, but I think that proves the point: if it's not a String it produces a nil.Tumular
To make this work on dictionary elements, at least in beta 3, you need to add a ! after sensor["latestValue"] in the example, otherwise a cryptic compile-time error about String not being a subtype of (String, AnyObject) occurs. If you are not confident that "latestValue" is in the dictionary (even with an NSNull value), you’ll need to split out a separate optional-unpacking step first.Dorothydorp
That saved some Time :)Dodecasyllable
M
37

You could also use is to check for the presence of a null:

if sensor["latestValue"] is NSNull {
    // do something with null JSON value here
}
Moray answered 2/6, 2016 at 7:11 Comment(0)
A
11

I'm using this combination and it also checks if object is not "null".

func isNotNull(object: AnyObject?) -> Bool {
    guard let object = object else { return false }
    return isNotNSNull(object) && isNotStringNull(object)
}

func isNotNSNull(object: AnyObject) -> Bool {
    object.classForCoder != NSNull.classForCoder()
}

func isNotStringNull(object: AnyObject) -> Bool {
    guard let object = object as? String where object.uppercaseString == "NULL" else {
        return true
    }
    return false
}

It's not that pretty as extension but work as charm :)

Ation answered 15/1, 2016 at 13:32 Comment(0)
C
9

NSNull is a class like any other. Thus you can use is or as to test an AnyObject reference against it.

Thus, here in one of my apps I have an NSArray where every entry is either a Card or NSNull (because you can't put nil in an NSArray). I fetch the NSArray as an Array and cycle through it, switching on which kind of object I get:

for card:AnyObject in arr {
    switch card { // how to test for different possible types
    case let card as NSNull:
        // do one thing
    case let card as Card:
        // do a different thing
    default:
        fatalError("unexpected object in card array") // should never happen!
    }
}

That is not identical to your scenario, but it is from a working app converted to Swift, and illustrates the full general technique.

Cranage answered 19/6, 2014 at 16:28 Comment(0)
T
8

my solution for now:

func isNull(someObject: AnyObject?) -> Bool {
    guard let someObject = someObject else {
        return true
    }

    return (someObject is NSNull)
}

tests look good so far...

tests look good so far

Turnpike answered 17/6, 2016 at 6:48 Comment(0)
H
3

I had a very similar problem and solved it with casting to the correct type of the original NSDictionary value. If your service returns a mixed type JSON object like this

{"id":2, "name":"AC Vent Temp", ...}

you'll have to fetch it's values like that.

var id:int = sensor.valueForKey("id") as Int;
var name:String? = sensor.valueForKey("name") as String;

This did solve my problem. See BAD_INSTRUCTION within swift closure

Haiduk answered 4/6, 2014 at 19:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.