使用 JSONSerialization() 动态计算布尔值

Using JSONSerialization() to dynamically figure out boolean values

我从服务器(或文件)得到一个 JSON 字符串。

我想解析 JSON 字符串并 动态地 找出每个值类型。

但是,对于布尔值,JSONSerialization只是将值转换为01,代码无法区分“0”是否为DoubleIntBool

我想在不明确知道特定键对应于 Bool 值的情况下识别该值是否为 Bool。我做错了什么,或者我可以做些什么不同的事情?

// 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

相关:

这种混乱是 "feature" 内置于 Swift<->Objective-C 桥中的所有奇妙魔法的结果。具体来说,isas 关键字的行为方式与您预期的不同,因为 JSONSerialization 对象实际上是用 Objective-C 编写的,并没有存储这些数字作为 Swift Ints、Doubles 或 Bools,而不是作为 NSNumber 对象,桥神奇地使 isasNSNumber 转换为可以转换为的任何 Swift 数字类型。这就是为什么 is 为每个 NSNumber 类型提供 true 的原因。

幸运的是,我们可以通过将数字值转换为 NSNumber 来解决这个问题,从而避免桥接。从那里,我们 运行 进入 更多 桥接恶作剧,因为 NSNumber 是 toll-free 桥接到布尔值的 CFBoolean,并且 CFNumber 对于大多数其他事情。因此,如果我们跳过所有障碍以进入 CF 级别,我们可以执行以下操作:

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")
    }
}

当您使用 JSONSerialization 时,任何布尔值(truefalse)都会转换为 NSNumber 个实例,这就是使用 is Double 的原因、is Intis Bool 全部 return 为真,因为 NSNumber 可以转换为所有这些类型。

您还会在 JSON.

中获得实际数字的 NSNumber 实例

但好消息是,在现实中,您实际上得到了 NSNumber 的特殊内部子类。布尔值实际上给你 __NSCFBoolean,而实际数字给你 __NSCFNumber。当然,您实际上并不想检查那些内部类型。

这是一个更完整的示例,显示了上述内容以及一个可行的解决方案,用于检查实际布尔值与 "normal" 数字。

let jsonString = "{\"boolean_key\" : true, \"int_key\" : 1}"
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String:Any]

print(type(of: json["boolean_key"]!)) // __NSCFBoolean
json["boolean_key"] is Double // true
json["boolean_key"] is Int // true
json["boolean_key"] is Bool // true

print(type(of: json["int_key"]!)) // __NSCFNumber
json["int_key"] is Double // true
json["int_key"] is Int // true
json["int_key"] is Bool // true

print(type(of: json["boolean_key"]!) == type(of: NSNumber(value: true))) // true
print(type(of: json["boolean_key"]!) == type(of: NSNumber(value: 1))) // false
print(type(of: json["int_key"]!) == type(of: NSNumber(value: 0))) // true
print(type(of: json["int_key"]!) == type(of: NSNumber(value: true))) // false

因为 JSONSerialization 将每个值转换为 NSNumber,这可以通过尝试找出每个 NSNumber 实例下面的内容来实现:

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

extension NSNumber {
    var isBool: Bool {
        return type(of: self) == type(of: NSNumber(booleanLiteral: true))
    }
}

(json["boolean_key"] as! NSNumber).isBool // true
(json["integer_key"] as! NSNumber).isBool // false

(注意:我在输入时已经得到了类似的[更好的]答案,但我想把我的答案留给其他寻找不同方法的人)