使用JSONSerialization()动态确定布尔值

12

我从服务器(或文件)获取了一个JSON字符串。

我想解析这个JSON字符串,动态地确定每个值的类型。

然而,在布尔值方面,JSONSerialization只是将值转换为01,代码无法区分“0”是DoubleInt还是Bool

我希望能够识别出值是否为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

相关:

3个回答

13

这种混淆是由于Swift与Objective-C桥连中所有奇妙的魔法所带来的“特性”导致的。具体来说,isas关键字的行为并不像你期望的那样,因为实际上是用Objective-C编写的JSONSerialization对象将这些数字存储为NSNumber对象,而桥连会自动将isas转换成任何可以被转换为Swift数值类型的NSNumber对象。因此,is对于每个NSNumber类型都会返回true

幸运的是,我们可以通过将数字值强制转换为NSNumber,从而避免使用桥连。但是,在这里我们会遇到更多的桥连乱搞,因为对于布尔值,NSNumber是无需桥接的CFBoolean,而对于大多数其他事物,NSNumber是无需桥接的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")
    }
}

非常感谢,我开始考虑类似的路线,因为在对字典进行漂亮打印时,它能够打印出它们是布尔值的事实。 - kgaidis

7
当您使用JSONSerialization时,任何布尔值(truefalse)都会转换为NSNumber实例,这就是为什么使用is Doubleis Intis Bool都返回true的原因,因为NSNumber可以转换为所有这些类型。
您还将获得JSON中实际数字的NSNumber实例。
但好消息是,实际上您实际上会获得NSNumber的特殊内部子类。布尔值实际上会给你__NSCFBoolean,而实际数字会给你__NSCFNumber。当然,您实际上不想检查那些内部类型。
以下是更完整的示例,显示上述内容以及可用于检查实际布尔值与“正常”数字的可行解决方案。
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

1
如果你到它们公开的 CF 级别下,实际上可以检查这些内部类型;请参见我的答案。 - Charles Srstka
@CharlesSrstka 我并没有说你不能检查内部类型。我建议你不要这样做,因为它们是内部的。你的检查没问题。我的意思是不要检查 __NSCFBoolean - rmaddy
__NSCFBoolean只是一个方便的桥接工具,用于将CFBoolean进行无缝转换。我们可以轻松地对其进行检查。 - Charles Srstka
非常感谢,我开始考虑相似的路线,因为在对字典进行漂亮打印时,它能够打印出它们是布尔值的事实。 - kgaidis
我喜欢你没有“硬编码”实际类型的事实,所以这似乎是最安全的方法。 - kgaidis

5
由于JSONSerialization将每个值转换为NSNumber,因此可以通过尝试找出每个NSNumber实例的底层类型来实现此目标:https://dev59.com/al0a5IYBdhLWcg3wipJA#30223989
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

(注:当我打这个回答时,已经有类似的[更好的]答案了,但我觉得留下我的答案,为那些希望看到不同方法的人留下参考)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接