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:
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 Int
s, Double
s, or Bool
s, but instead as NSNumber
objects, and the bridge just magically makes is
and as
convert NSNumber
s 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")
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With