简体   繁体   English

"使用 Codable 将空字符串解码为 nil"

[英]Decoding empty string into nil with Codable

normal case:正常情况:

{
    "maintenance": true
}

{
    "maintenance": false
}

You can try你可以试试

struct Root: Codable {
    var maintenance: Bool? 
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self) 
        do {
            self.maintenance = try container.decode(Bool.self, forKey: .maintenance) 
        }
        catch { 
        } 
    }
}

In most cases, I'd probably use SeaSpell or Sh_Khan's solutions.在大多数情况下,我可能会使用 SeaSpell 或 Sh_Khan 的解决方案。 Bool?<\/code> is usually not the right type, but in this case it seems precisely what you mean (you seem to want to keep track of whether the value was set or not, rather than defaulting to something).通常不是正确的类型,但在这种情况下,它似乎正是您的意思(您似乎想跟踪该值是否已设置,而不是默认设置)。 But those approaches do require a custom decoder for the whole type which might be inconvenient.但是这些方法确实需要整个类型的自定义解码器,这可能不方便。 Another approach would be to define a new type just for this 3-way value:另一种方法是为这个 3-way 值定义一个新类型:

enum Maintenance {
    case `true`, `false`, unset
}

What you need is to try to decode your Bool, catch the error, try to decode a string and check if it is an empty string otherwise throw the error.您需要尝试解码您的 Bool,捕获错误,尝试解码字符串并检查它是否为空字符串,否则抛出错误。 This will make sure you don't discard any decoding error even if it is a string but not empty:这将确保您不会丢弃任何解码错误,即使它是一个字符串但不是空的:


struct Demo: Codable {
    var maintenance: Bool?
}

struct Root: Codable {
    var maintenance: Bool?
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            maintenance = try container.decode(Bool.self, forKey: .maintenance)
        } catch {
            guard try container.decode(String.self, forKey: .maintenance) == "" else {
                throw error
            }
            maintenance = nil
        }
    }
}

Playground testing:游乐场测试:

let json1 = """
{
    "maintenance": true
}
"""
let json2 = """
{
    "maintenance": false
}
"""
let json3 = """
{
    "maintenance": ""
}
"""
let json4 = """
{
    "maintenance": "false"
}
"""

do {
    let root1 = try JSONDecoder().decode(Root.self, from: Data(json1.utf8))
    print("root1", root1)
} catch {
    print(error)
}

do {
    let root2 = try JSONDecoder().decode(Root.self, from: Data(json2.utf8))
    print("root2", root2)
} catch {
    print(error)
}

do {
    let root3 = try JSONDecoder().decode(Root.self, from: Data(json3.utf8))
    print("root3", root3)
} catch {
    print(error)
}

do {
    let root4 = try JSONDecoder().decode(Root.self, from: Data(json4.utf8))
    print("root4", root4)
} catch {
    print(error)
}

This will print这将打印

root1 Root(maintenance: Optional(true)) root1 根(维护:可选(真))
root2 Root(maintenance: Optional(false)) root2 根(维护:可选(假))
root3 Root(maintenance: nil) root3 根(维护:无)
typeMismatch(Swift.Bool, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "maintenance", intValue: nil)], debugDescription: "Expected to decode Bool but found a string/data instead.", underlyingError: nil)) typeMismatch(Swift.Bool, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "maintenance", intValue: nil)], debugDescription: "期望解码 Bool 但找到了一个字符串/数据。",底层错误: nil) )

You really only have a couple of decent options that I know of.我知道你真的只有几个不错的选择。

  1. do codable init yourself and loose the free one.自己做可编码的初始化并释放免费的。

     struct Root: Codable { var maintenance: Bool? init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let value = (try? container.decode(Bool.self, forKey: .maintenance) ?? "") self.maintenance = (value.isEmpty() || value == "false" ) ? nil : true } }

For obvious reasons this may not be ideal, especially if you have a lot of other variables to decode.由于显而易见的原因,这可能并不理想,特别是如果您有很多其他变量要解码。 The other option is to use a getter and add a variable to store the string optional.另一种选择是使用 getter 并添加一个变量来存储可选的字符串。

  1. Calculated var计算变量

     private var _maintenance: String? var maintenance: Bool { get { ((_maintenance ?? "").isEmpty || _maintenance == "false") ? false : true } }

This solution is more ideal because you only need to change coding keys and add a var.这种解决方案更理想,因为您只需要更改编码键并添加一个 var。

Bool?布尔? is a very bad idea unless you know exactly what you are doing, because you have three possible values: true, false and nil.除非您确切地知道自己在做什么,否则这是一个非常糟糕的主意,因为您有三个可能的值:true、false 和 nil。

You can't use it directly in an if statement.您不能直接在 if 语句中使用它。 You can compare b == true, b == false or b == nil and I expect that b == false will not produce the result you expect.您可以比较 b == true、b == false 或 b == nil,我希望 b == false 不会产生您期望的结果。

You want to use a three-value type for two possible values, which is asking for trouble.您想对两个可能的值使用三值类型,这是自找麻烦。

I'd get the API changed because that's just bad design to have a field with two different types.我会更改 API,因为具有两种不同类型的字段只是糟糕的设计。 It'd be much more sensible to have it as something like将它作为类似的东西会更明智

maintenance: { active: true }<\/code>

And for the case where there's no station, either set it as null or remove the field对于没有站点的情况,将其设置为 null 或删除该字段

"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM