一尘不染

如果单元素解码失败,Swift JSONDecode解码数组也会失败

swift

在使用Swift4和Codable协议时,我遇到了以下问题-似乎没有办法允许JSONDecoder跳过数组中的元素。例如,我有以下JSON:

[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]

和一个可 编码的 结构:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

解码此json时

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)

结果products为空。这是可以预期的,因为JSON中的第二个对象没有"points"键,而pointsGroceryProductstruct中不是可选的。

问题是如何允许JSONDecoder“跳过”无效对象?


阅读 240

收藏
2020-07-07

共1个答案

一尘不染

一种选择是使用包装器类型,尝试对给定值进行解码。nil如果不成功,则存储:

struct FailableDecodable<Base : Decodable> : Decodable {

    let base: Base?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.base = try? container.decode(Base.self)
    }
}

然后,我们可以解码这些数组,并GroceryProductBase占位符中填写以下内容:

import Foundation

let json = """
[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
""".data(using: .utf8)!


struct GroceryProduct : Codable {
    var name: String
    var points: Int
    var description: String?
}

let products = try JSONDecoder()
    .decode([FailableDecodable<GroceryProduct>].self, from: json)
    .compactMap { $0.base } // .flatMap in Swift 4.0

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

然后,我们.compactMap { $0.base }用于过滤掉nil元素(那些在解码时引发错误的元素)。

这将创建的中间数组[FailableDecodable<GroceryProduct>],这不应该成为问题;但是,如果您希望避免这种情况,可以始终创建另一个包装器类型,该类型将解码和解包来自无键容器的每个元素:

struct FailableCodableArray<Element : Codable> : Codable {

    var elements: [Element]

    init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var elements = [Element]()
        if let count = container.count {
            elements.reserveCapacity(count)
        }

        while !container.isAtEnd {
            if let element = try container
                .decode(FailableDecodable<Element>.self).base {

                elements.append(element)
            }
        }

        self.elements = elements
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(elements)
    }
}

然后,您将解码为:

let products = try JSONDecoder()
    .decode(FailableCodableArray<GroceryProduct>.self, from: json)
    .elements

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]
2020-07-07