JSON and Swift: Making the Bool magic happen in Aldwych (updated with NSNumber extension)


Aldwych is a JSON parsing repo I've written for Swift, which is hosted on GitHub.

The problem

NSJSONSerialization can write Bool values but it imports them as NSNumber values of zero or one, representing false and true respectively. The problem is further exacerbated by the fact that all NSNumbers can be cast to Bool and all Bool values can be cast to NSNumber. This makes it very difficult to distinguish between them.

The solution

An NSNumber created from a Bool does retain enough knowledge about itself, however, to know that it is equal in value and type to an NSNumber instantiated with a Bool of the same kind. This is thanks to its inheritance from NSValue. So while the following will return true,
NSNumber(bool: true).isEqualToNumber(NSNumber(integer: 1)) // true
if we instead use the NSValue method isEqualToValue(),
NSNumber(bool: true).isEqualToValue(NSNumber(integer: 1)) // false
false will be returned. This enables the following code to be written:
else if let v = v as? NSNumber  {
                // test to see if a bool
                if handleBool && v.isEqualToValue(NSNumber(bool: true)) || handleBool && v.isEqualToValue(NSNumber(bool: false))  {
                    if boolDict == nil {
                        boolDict = Dictionary()
                    }
                    if boolDict != nil {
                        boolDict![k] = Bool(v)
                    }
                }
                else {
                if numDict == nil {
                    numDict = Dictionary()
                }
                if numDict != nil {
                    numDict![k] = v
                    
                    }
                }
            }
And this is all we need to distinguish between a Bool and a number. The rest of the code follows the same pattern as every other type that Aldwych handles.

Conclusion

Not only does this knowledge help in distinguishing Bool from a number but it helps us distinguish Doubles from Integers, for example:
NSNumber(double: 1).isEqualToNumber(NSNumber(integer: 1)) // true
NSNumber(double: 1).isEqualToValue(NSNumber(integer: 1)) // false
And this is super useful to know when working with the NSNumbers that are generated by NSJSONSerialization and in turn Aldwych.

Update

The true/false test has been working perfectly, but I posted a question to StackOverflow just to check there wasn't a different way of testing for Bool. And as a result I've updated Aldwych with a simpler and stronger approach through an NSNumber extension:
extension NSNumber {
    func isBoolNumber() -> Bool
    {
        let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean
        let numID = CFGetTypeID(self) // the type ID of num
        return numID == boolID
    }
}
which enables me to replace this line of code
if handleBool && v.isEqualToValue(NSNumber(bool: true)) || handleBool && v.isEqualToValue(NSNumber(bool: false))
with this
if handleBool && v.isBoolNumber()
Rather than explain the logic here, I recommend you read it directly on SO.


Endorse on Coderwall

Comments