【问题标题】:Any way for Swift 4 JSON Decoder to not throw for an array containing an unrecognized enum value?Swift 4 JSON解码器有什么办法不抛出包含无法识别的枚举值的数组?
【发布时间】:2018-01-25 12:29:13
【问题描述】:

我正在尝试使用 Swift 4 的新 JSON 解码来解析来自远程服务器的 JSON。 JSON 模式包括枚举值,其中一些我实际上并不需要用于我的目的,我想忽略它。此外,我还希望足够健壮,以便当 JSON 架构发生变化时,我仍然能够读取尽可能多的数据。

问题是,当我尝试解析包含枚举的任何内容的数组时,除非每个枚举值都与我的枚举的文字完全匹配,否则解码器会抛出异常,而不是跳过它无法解析的数据。

这是一个简单的例子:

enum Size: String, Codable {
    case large = "large"
    case small = "small"
}

enum Label: String, Codable {
    case kitchen = "kitchen"
    case bedroom = "bedroom"
    case livingRoom = "living room"
}

struct Box: Codable {
    var contents: String
    var size: Size
    var labels: [Label]
}

当我解析完全符合我的 Size 枚举的数据时,我得到了预期的结果:

let goodJson = """
[
  {
    "contents": "pillows",
    "size": "large",
    "labels": [
        "bedroom"
    ]
  },
  {
    "contents": "books",
    "size": "small",
    "labels": [
        "bedroom",
        "living room"
    ]
  }
]
""".data(using: .utf8)!

let goodBoxes = try? JSONDecoder().decode([Box?].self, from: goodJson)
// returns [{{contents "pillows", large, [bedroom]}},{{contents "books", small, [bedroom, livingRoom]}}]

但是,如果有不符合枚举的内容,解码器会抛出异常,我什么也得不到。

let badJson = """
[
  {
    "contents": "pillows",
    "size": "large",
    "labels": [
        "bedroom"
    ]
  },
  {
    "contents": "books",
    "size": "small",
    "labels": [
        "bedroom",
        "living room",
        "office"
    ]
  },
  {
    "contents": "toys",
    "size": "medium",
    "labels": [
        "bedroom"
    ]
  }
]
""".data(using: .utf8)!

let badBoxes = try? JSONDecoder().decode([Box?].self, from: badJson)    // returns nil

理想情况下,在这种情况下,我想取回尺寸符合“小”或“大”的 2 件物品,并且缠绕的第二件物品有 2 个有效标签,“卧室”和“客厅”。

如果我为 Box 实现自己的 init(from: decoder),我可以自己解码标签并丢弃任何不符合我的枚举的标签。但是,我无法弄清楚如何解码 [Box] 类型以忽略无效框,而无需实现自己的解码器并自己解析 JSON,这违背了使用 Codable 的目的。

有什么想法吗?

【问题讨论】:

  • 我有一个类似的情况,我只想忽略无效的数组元素,不希望解析器在整个数组上失败。真的没有办法跳过无效元素,而不是完全“扔掉”数组并使整个解析失败吗? :-/

标签: json decoder swift4


【解决方案1】:

有点痛苦,但你可以自己编写解码。

import Foundation

enum Size: String, Codable {
    case large = "large"
    case small = "small"
}

enum Label: String, Codable {
    case kitchen = "kitchen"
    case bedroom = "bedroom"
    case livingRoom = "living room"
}

struct Box: Codable {
    var contents: String = ""
    var size: Size = .small
    var labels: [Label] = []

    init(from decoder: Decoder) throws {
        guard let container = try? decoder.container(keyedBy: CodingKeys.self) else {
            return
        }

        contents = try container.decode(String.self, forKey: .contents)
        let rawSize = try container.decode(Size.RawValue.self, forKey: .size)
        size = Size(rawValue: rawSize) ?? .small

        var labelsContainer = try container.nestedUnkeyedContainer(forKey: .labels)
        while !labelsContainer.isAtEnd {
            let rawLabel = try labelsContainer.decode(Label.RawValue.self)
            if let label = Label(rawValue: rawLabel) {
                labels.append(label)
            }
        }
    }
}

extension Box: CustomStringConvertible {
    var description: String {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        do {
            let data = try encoder.encode(self)
            if let jsonString = String(data: data, encoding: .utf8) {
                return jsonString
            }
        } catch {
            return ""
        }
        return ""
    }
}

let badJson = """
[
  {
    "contents": "pillows",
    "size": "large",
    "labels": [
        "bedroom"
    ]
  },
  {
    "contents": "books",
    "size": "small",
    "labels": [
        "bedroom",
        "living room",
        "office"
    ]
  },
  {
    "contents": "toys",
    "size": "medium",
    "labels": [
        "bedroom"
    ]
  }
]
""".data(using: .utf8)!

do {
    let badBoxes = try JSONDecoder().decode([Box].self, from: badJson)
    print(badBoxes)
} catch {
    print(error)
}

输出:

[{
  "labels" : [
    "bedroom"
  ],
  "size" : "large",
  "contents" : "pillows"
}, {
  "labels" : [
    "bedroom",
    "living room"
  ],
  "size" : "small",
  "contents" : "books"
}, {
  "labels" : [
    "bedroom"
  ],
  "size" : "small",
  "contents" : "toys"
}]

【讨论】:

    【解决方案2】:

    我承认这不是最漂亮的解决方案,但这是我想出的,我想我会分享。我在 Array 上创建了一个允许这样做的扩展。最大的缺点是您必须对 JSON 数据进行解码然后再对其进行编码。

    extension Array where Element: Codable {
        public static func decode(_ json: Data) throws -> [Element] {
            let jsonDecoder = JSONDecoder()
            var decodedElements: [Element] = []
            if let jsonObject = (try? JSONSerialization.jsonObject(with: json, options: [])) as? Array<Any> {
                for json in jsonObject {
                   if let data = try? JSONSerialization.data(withJSONObject: json, options: []), let element = (try? jsonDecoder.decode(Element.self, from: data)) {
                        decodedElements.append(element)
                    }
                }
            }
            return decodedElements
        }
    }
    

    您可以将此扩展与任何符合 Codable 的内容一起使用,并且应该可以解决您的问题。

    [Box].decode(json)
    

    很遗憾,我不知道这将如何解决标签不正确的问题,您必须照您说的做并覆盖 init(from: Decoder) 以确保您的标签有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多