Swift Декодирует тип данных с различными форматами - PullRequest
0 голосов
/ 25 сентября 2018

Мне возвращают логическое значение в «нескольких» различных форматах с сервера (для одной и той же структуры и поля). Я знаю, что это нелепо, но мне нужно найти способ аккуратно с ним справиться.

Поэтому для десериализации я делаю что-то вроде (пример программы):

import Foundation

struct Foo: Codable {
    var isOpen: Bool?

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        isOpen = try container.decodeIfPresent(Bool.self, forKey: .isOpen)
    }

    enum CodingKeys: String, CodingKey {
        case isOpen
    }
}

//He sends any one of these..
let json1 = "{ \"isOpen\": \"true\" }"
let json2 = "{ \"isOpen\": \"false\" }"
let json3 = "{ \"isOpen\": true }"
let json4 = "{ \"isOpen\": false }"
let json5 = "{ \"isOpen\": null }"
let json6 = "{ \"isOpen\": \"null\" }"
let json7 = "{ \"isOpen\": \"<null>\" }"

//He doesn't send this one.. but I wouldn't be surprised if I got it so I added it for fun (serializing the below `json8` and `json9` is not required for an answer).. :)

let json8 = "{ \"isOpen\": 0 }"
let json9 = "{ \"isOpen\": 1 }"

let json = [json1, json2, json3, json4, json5, json6, json7, json8, json9]
for js in json {
    if let rawData = js.data(using: .utf8) {
        do {
            let foo = try JSONDecoder().decode(Foo.self, from: rawData)
            if let isOpen = foo.isOpen {
                print("\(isOpen)\n\n")
            } else {
                print("State Unknown\n\n")
            }
        } catch {
            print("\(error)\n\n")
        }
    }
}

Теперь, если я использую Swift Codable (который уже используется всеми нашими структурами данных), мы получим несоответствие типов иошибка / исключение выдается.Я думал о том, чтобы попытаться перехватить каждый случай и попробовать другой декодер с другим типом, но тогда это закончится примерно так:

do {
    isOpen = try container.decode(Bool.self, forKey: .isOpen)
}
catch {
    do {
        isOpen = try container.decode(Int.self, forKey: .isOpen) != 0
    }
    catch {
        do {
            isOpen = Bool(try container.decode(String.self, forKey: .isOpen))
        }
        catch {
            do {
                isOpen = try container.decodeIfPreset(Bool.self, forKey: .isOpen)  ?? GiveUpAndAssignDefaultValueHere..
            }
            catch {
                isOpen = nil //no idea..
            }
        }
    }
}

Тогда я подумал о том, чтобы сначала преобразовать его в строку, а затем попытаться разобратьчто вместо этого я закончил с (по крайней мере, лучше, чем выше):

do {
    isOpen = try container.decode(Bool?.self, forKey: .isOpen)
}
catch {
    do {
        isOpen = Bool(try container.decode(String.self, forKey: .isOpen))
    }
    catch {
        isOpen = try container.decode(Int.self, forKey: .isOpen) != 0
    }
}

но, конечно, есть лучший способ?Есть идеи ???

Ответы [ 3 ]

0 голосов
/ 25 сентября 2018

Вместо catch ошибок я бы условно связал типы

struct Foo: Codable {
    var isOpen: Bool?

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let boolOpen = try? container.decode(Bool.self, forKey: .isOpen) {
            isOpen = boolOpen
        } else if let intOpen = try? container.decode(Int.self, forKey: .isOpen) {
            isOpen = intOpen == 1
        } else if let stringOpen = try? container.decode(String.self, forKey: .isOpen) {
            switch stringOpen {
            case "true", "1": isOpen = true
            case "false", "0": isOpen = false
            default : isOpen = nil
            }
        } else {
            isOpen = nil
        }
    }
}
0 голосов
/ 26 сентября 2018

Другой метод, тот же принцип, просто для удовольствия.Это очень хорошо

    init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)
_  =  [ try? container.decode(Bool.self, forKey: .isOpen),
    try?  container.decode(String.self, forKey: .isOpen),
    try? container.decode(Int.self, forKey: .isOpen)].first{
switch $0 {
case is Bool:
    self.isOpen  = $0 as? Bool
    return true
case is Int:
    self.isOpen  = ($0 as! Int) == 0 ? false : (($0 as! Int) == 1 ? true : nil)
    return true
case is String:
    self.isOpen  = Bool.init($0 as! String)
    return true
default:
    return false
}
    }
}
0 голосов
/ 25 сентября 2018

Новое предложение состоит в том, чтобы декодировать многозначные значения с использованием того же CodingKey isOpen

код будет выглядеть примерно так:

struct Foo: Codable {
var isOpen: Bool?
private var isOpenInty: Int?
private var isOpenStringy: String?
init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    do {
        isOpen = try container.decodeIfPresent(Bool.self, forKey: .isOpen)

    }catch {
        do {
            isOpenInty = try container.decodeIfPresent(Int.self, forKey: .isOpen)
            if isOpenInty == 0  {isOpen = true} else {isOpen = false}
        }catch {
            isOpenStringy = try container.decodeIfPresent(String.self, forKey: .isOpen)
            if isOpenStringy == "true" {isOpen = true} else {isOpen = false}
        }
    }
}

И на основе любого из этих значений установите isOpen значение.

Просто другой способ обращения с этим делом.

почти такая же идея, как у вас здесь,

   do {
        isOpen = try container.decode(Bool?.self, forKey: .isOpen)
    }
    catch {
        do {
            isOpen = Bool(try container.decode(String.self, forKey: .isOpen))
        }
        catch {
            isOpen = try container.decode(Int.self, forKey: .isOpen) != 0
        }
    }

, но ваш код выдает State Unknown в случае нуля, код над ним просто обрабатывает его, если он нулевой, как ложь

...