Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using JSONSerialization() to dynamically figure out boolean values

I am getting a JSON string from the server (or file).

I want to parse that JSON string and dynamically figure out each of the value types.

However, when it comes to boolean values, JSONSerialization just converts the value to 0 or 1, and the code can't differentiate whether "0" is a Double, Int, or Bool.

I want to recognize whether the value is a Bool without explicitly knowing that a specific key corresponds to a Bool value. What am I doing wrong, or what could I do differently?

// What currently is happening:
let jsonString = "{\"boolean_key\" : true}"
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String:Any]

json["boolean_key"] is Double // true
json["boolean_key"] is Int // true
json["boolean_key"] is Bool // true

// What I would like to happen is below (the issue doesn't happen if I don't use JSONSerialization):
let customJson: [String:Any] = [
    "boolean_key" : true
]

customJson["boolean_key"] is Double // false
customJson["boolean_key"] is Int // false
customJson["boolean_key"] is Bool // true

Related:

  • How do I get NSJSONSerialization to output a boolean as true or false?
  • Is there a correct way to determine that an NSNumber is derived from a Bool using Swift?
like image 633
kgaidis Avatar asked Apr 04 '18 00:04

kgaidis


1 Answers

This confusion is a result of the "feature" of all the wonderful magic built into the Swift<->Objective-C bridge. Specifically, the is and as keywords don't behave the way you'd expect, because the JSONSerialization object, being actually written in Objective-C, is storing these numbers not as Swift Ints, Doubles, or Bools, but instead as NSNumber objects, and the bridge just magically makes is and as convert NSNumbers to any Swift numeric types that they can be converted to. So that is why is gives you true for every NSNumber type.

Fortunately, we can get around this by casting the number value to NSNumber instead, thus avoiding the bridge. From there, we run into more bridging shenanigans, because NSNumber is toll-free bridged to CFBoolean for Booleans, and CFNumber for most other things. So if we jump through all the hoops to get down to the CF level, we can do things like:

if let num = json["boolean_key"] as? NSNumber {
    switch CFGetTypeID(num as CFTypeRef) {
        case CFBooleanGetTypeID():
            print("Boolean")
        case CFNumberGetTypeID():
            switch CFNumberGetType(num as CFNumber) {
            case .sInt8Type:
                print("Int8")
            case .sInt16Type:
                print("Int16")
            case .sInt32Type:
                print("Int32")
            case .sInt64Type:
                print("Int64")
            case .doubleType:
                print("Double")
            default:
                print("some other num type")
            }
        default:
            print("Something else")
    }
}
like image 174
Charles Srstka Avatar answered Jan 26 '23 08:01

Charles Srstka