Проверка типов на значения Int и Bool некорректно возвращаются в Swift 4.2 - PullRequest
0 голосов
/ 30 ноября 2018

В настоящее время у меня есть проблема со Swift 4.2, которую я хотел попросить обратиться за помощью к людям.

В настоящее время я десериализирую ответ JSON, представляющий собой словарь, ключи которого имеют тип Stringи чьи значения могут быть типа Int или Bool.Хорошим примером является следующий:

{
    "number_of_likes": 0,
    "is_liked": true

}

Когда я десериализую объект, ответ JSON имеет тип [String: Any], что ожидается.

Задача: Мне нужно создатьВ массиве указывается, какие ключи имеют тип Bool и установлены на true.

Проблема:

При запуске следующего кода с использованием ответа, выделенного выше:

guard let json = json as? [String: Any] else {
    return
}

for key in dict.keys {

    print("KEY: \(key)")

    let value = dict[key]

    if value is Int {
        print("It is an integer")
    }

    if value is Bool {
        print("It is a bool")
    }

}

возвращаемые строки

Консоль печатает это:

? t+20.765 KEY: number_of_likes
? t+20.765 It is an integer
? t+20.765 It is a bool

? t+20.765 KEY: is_liked
? t+20.765 It is an integer
? t+20.765 It is a bool

Как видите, значение 0 Int заставляет консоль печатать, что онотипа Bool и Int, в то время как значение true Bool заставляет консоль печатать, что оно имеет тип Int и Bool.

Для контекста я нашел следующеевопрос по StackOverflow, но ответ не сработал, если я не проверю тип CFBool, а не тип Bool.

Можно ли различить Bool и Int в Swift?

Может кто-нибудь сказать мне, если я делаю что-то не так, или это проблема сSwift 4.2?

Я определенно благодарен за помощь, поэтому спасибо всем, кто это прочитал.

Ответы [ 2 ]

0 голосов
/ 30 ноября 2018

К сожалению, вы не можете решить эту проблему, не опустившись на довольно низкий уровень, используя либо базовые типы Foundation, либо строки Objective-C @encode.

Проблема в том, что под прикрытием FoundationJSON-сериализация использует NSNumber для переноса как целых, так и логических значений.Таким образом, JSON 0 и JSON true оба преобразуются в NSNumber объекты, и Swift желает преобразовать любой из этих NSNumber объектов в Int или Bool по запросу.

Однако логические значения JSON фактически преобразуются в подкласс NSNumber, называемый __NSCFBoolean, к которому относится тип CFBooleanRef (в Swift, CFBoolean):

import Foundation

let json = """
{
    "number_of_likes": 0,
    "is_liked": true

}
"""

let data = json.data(using: .utf8)!
let jso = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]

for key in jso.keys {
    if let value = jso[key] as? NSNumber {
        print("\(key) \(type(of: value)) \(String(cString: value.objCType))")
    }
}

Вывод:

number_of_likes __NSCFNumber q
is_liked __NSCFBoolean c

Не задокументировано, что сериализация Foundation JSON декодирует логические значения JSON в Core Foundation CFBoolean s, но вряд ли изменится.

Так что вот основной способ Foundationtest:

if let isLikedCF = jso["is_liked"] as CFTypeRef?,
    CFGetTypeID(isLikedCF) == CFBooleanGetTypeID()
{
    print("it's bool")
} else {
    print("it's not bool")
}

Здесь мы конвертируем значение из словаря JSON в CFTypeRef (который является ссылкой на любой базовый тип Foundation, и все, что возвращает JSONSerialization Foundation, этобесплатное соединение с типом Core Foundation), а затем проверка того, является ли идентификатор типа объекта Core Foundation идентификатором типа CFBoolean.

Другой способ проверки с использованием Core Foundation состоит в том, чтобы распознать, что существует только дваo CFBooleanRef значения, kCFBooleanTrue и kCFBooleanFalse.Вы можете увидеть, совпадает ли jso["is_liked"] as? NSNumber с одним из этих двух значений, используя ===:

if let isLikedNumber = jso["is_liked"] as? NSNumber,
    isLikedNumber === kCFBooleanTrue || isLikedNumber === kCFBooleanFalse
{
    print("it's bool")
} else {
    print("it's not bool")
}

Вы также можете проверить, проверив код типа Objective-C NSNumber.Вы применяете jso["is_liked"] as? NSNumber, запрашиваете objCType, конвертируете полученную C-строку в Swift String и сравниваете ее с "c".Если это так, это логическое значение.В противном случае это не так.

if let isLikedNumber = jso["is_liked"] as? NSNumber {
    if String(cString: isLikedNumber.objCType) == "c" {
        print("it's bool")
    } else {
        print("it's not bool")
    }
}

c происходит от @encode(BOOL) (в Objective-C), где BOOL - это typedef, равный signed char.Это действительно неясные вещи.Я бы рекомендовал пройти базовый тест (показанный выше), так как его легче понять и лучше документировать.

0 голосов
/ 30 ноября 2018

Вы должны сделать что-то вроде этого

import UIKit

var str = """
{
"number_of_likes": 0,
"is_liked": true
}
"""
struct JsonStruct: Decodable {
    var boolWithKey: [String: Bool]?
    var intWithKey: [String: Int]?
    init(from decoder: Decoder) {
        guard let container = try? decoder.container(keyedBy: CodingKeys.self) else {
            fatalError()
        }
        for key in container.allKeys {
            if let possibleInt = try? container.decode(Int.self, forKey: key) {
                intWithKey = [key.stringValue: possibleInt]
            }
            if let possibleBool = try? container.decode(Bool.self, forKey: key) {
                boolWithKey = [key.stringValue: possibleBool]
            }
        }
        print(container.allKeys)
    }

    struct CodingKeys: CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int?
        init?(intValue: Int) {
            return nil
        }
    }
}
let jsonData = str.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
let jsonStruct = try! jsonDecoder.decode(JsonStruct.self, from: jsonData)
print("\(jsonStruct.boolWithKey)")
print("\(jsonStruct.intWithKey)")

Вывод:

CodingKeys (stringValue: "number_of_likes", intValue: nil),

CodingKeys (stringValue: "is_liked", intValue: nil)]

Необязательно (["is_liked": true])

Необязательно (["number_of_likes": 0])

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...