Arrays 如果单元素解码失败,Swift JSONDecode解码阵列将失败

Arrays 如果单元素解码失败,Swift JSONDecode解码阵列将失败,arrays,json,swift,swift4,codable,Arrays,Json,Swift,Swift4,Codable,在使用Swift4和Codable协议时,我遇到了以下问题-似乎无法允许jsondeconder跳过数组中的元素。 例如,我有以下JSON: [ { "name": "Banana", "points": 200, "description": "A banana grown in Ecuador." }, { "name": "Orange" } ] 和一个可编码结构: 解码此json时 let

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

[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
和一个可编码结构:

解码此json时

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
生成的
产品
为空。这是意料之中的,因为JSON中的第二个对象没有
“points”
键,而
points
GroceryProduct
结构中不是可选的


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

不幸的是,swift4api没有针对
init(from:Decoder)
的可失败初始值设定项

我看到的唯一解决方案是实现自定义解码,为可选字段提供默认值,并使用所需数据进行可能的筛选:

struct GroceryProduct: Codable {
    let name: String
    let points: Int?
    let description: String

    private enum CodingKeys: String, CodingKey {
        case name, points, description
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        points = try? container.decode(Int.self, forKey: .points)
        description = (try? container.decode(String.self, forKey: .description)) ?? "No description"
    }
}

// for test
let dict = [["name": "Banana", "points": 100], ["name": "Nut", "description": "Woof"]]
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
    let decoder = JSONDecoder()
    let result = try? decoder.decode([GroceryProduct].self, from: data)
    print("rawResult: \(result)")

    let clearedResult = result?.filter { $0.points != nil }
    print("clearedResult: \(clearedResult)")
}
有两种选择:

  • 将结构的所有成员声明为可选,其键可能丢失

    struct GroceryProduct: Codable {
        var name: String
        var points : Int?
        var description: String?
    }
    
  • 编写自定义初始值设定项以在
    nil
    情况下指定默认值

    struct GroceryProduct: Codable {
        var name: String
        var points : Int
        var description: String
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            name = try values.decode(String.self, forKey: .name)
            points = try values.decodeIfPresent(Int.self, forKey: .points) ?? 0
            description = try values.decodeIfPresent(String.self, forKey: .description) ?? ""
        }
    }
    

  • 一种选择是使用试图解码给定值的包装器类型;存储
    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)
        }
    }
    
    然后我们使用
    .compactMap{$0.base}
    过滤掉
    nil
    元素(那些在解码时抛出错误的元素)

    这将创建一个中间数组
    [FailableDecodable]
    ,这不应该是一个问题;但是,如果您希望避免这种情况,则始终可以创建另一种包装器类型,该类型将解码和打开未命名容器中的每个元素:

    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)
        }
    }
    
    struct FailableCodableArray:Codable{
    变量元素:[元素]
    init(来自解码器:解码器)抛出{
    var container=try decoder.unkeydcontainer()
    变量元素=[Element]()
    如果let count=container.count{
    元素。保留容量(计数)
    }
    while!container.isattend{
    如果let元素=try容器
    .decode(FailableDecodable.self).base{
    元素。追加(元素)
    }
    }
    self.elements=元素
    }
    func encode(到编码器:编码器)抛出{
    var container=encoder.singleValueContainer()
    尝试container.encode(元素)
    }
    }
    
    然后,您将解码为:

    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.")
    //    )
    // ]
    
    let products=try JSONDecoder()
    .decode(FailableCodableArray.self,from:json)
    .要素
    印刷品(产品)
    // [
    //食品杂货(
    //名称:“香蕉”,点数:200,
    //描述:可选(“生长在厄瓜多尔的香蕉”)
    //    )
    // ]
    
    问题在于,在容器上迭代时,container.currentIndex不会增加,因此您可以尝试使用其他类型再次解码

    因为currentIndex是只读的,所以一个解决方案是自己递增它,成功地解码一个伪索引。我采用@Hamish解决方案,编写了一个带有自定义init的包装器

    此问题是当前的Swift错误:

    这里发布的解决方案是其中一条评论中的一个变通方法。 我喜欢这个选项,因为我在网络客户机上以相同的方式解析一组模型,并且我希望解决方案是其中一个对象的本地解决方案。也就是说,我还是希望其他的被丢弃

    我在github中解释得更好

    <代码>导入基础 让json=”“” [ { “名称”:“香蕉”, “分数”:200分, “描述”:“生长在厄瓜多尔的香蕉。” }, { “名称”:“橙色” } ] “”数据(使用:.utf8)! 私有结构dummycable:Codable{} 结构杂货:可编码 { var食品杂货:[食品杂货产品] init(来自解码器:解码器)抛出{ var groceries=[GroceryProduct]() var container=try decoder.unkeydcontainer() while!container.isattend{ 如果let route=try?container.decode(GroceryProduct.self){ 杂货。附加(路线) }否则{
    _=try?container.decode(DummyCodable.self)/我将@sophy swicz解决方案经过一些修改,放入了一个易于使用的扩展中

    fileprivate struct DummyCodable: Codable {}
    
    extension UnkeyedDecodingContainer {
    
        public mutating func decodeArray<T>(_ type: T.Type) throws -> [T] where T : Decodable {
    
            var array = [T]()
            while !self.isAtEnd {
                do {
                    let item = try self.decode(T.self)
                    array.append(item)
                } catch let error {
                    print("error: \(error)")
    
                    // hack to increment currentIndex
                    _ = try self.decode(DummyCodable.self)
                }
            }
            return array
        }
    }
    extension KeyedDecodingContainerProtocol {
        public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
            var unkeyedContainer = try self.nestedUnkeyedContainer(forKey: key)
            return try unkeyedContainer.decodeArray(type)
        }
    }
    
    对于上述示例:

    let json = """
    [
        {
            "name": "Banana",
            "points": 200,
            "description": "A banana grown in Ecuador."
        },
        {
            "name": "Orange"
        }
    ]
    """.data(using: .utf8)!
    
    struct Groceries: Codable 
    {
        var groceries: [GroceryProduct]
    
        init(from decoder: Decoder) throws {
            var container = try decoder.unkeyedContainer()
            groceries = try container.decodeArray(GroceryProduct.self)
        }
    }
    
    struct GroceryProduct: Codable {
        var name: String
        var points: Int
        var description: String?
    }
    
    let products = try JSONDecoder().decode(Groceries.self, from: json)
    print(products)
    

    我将创建一个新类型
    Throwable
    ,它可以包装符合
    可解码的任何类型:

    enum Throwable<T: Decodable>: Decodable {
        case success(T)
        case failure(Error)
    
        init(from decoder: Decoder) throws {
            do {
                let decoded = try T(from: decoder)
                self = .success(decoded)
            } catch let error {
                self = .failure(error)
            }
        }
    }
    
    extension Throwable {
        var value: T? {
            switch self {
            case .failure(_):
                return nil
            case .success(let value):
                return value
            }
        }
    }
    
    其中,
    value
    是在可丢弃的
    扩展中引入的计算属性:

    enum Throwable<T: Decodable>: Decodable {
        case success(T)
        case failure(Error)
    
        init(from decoder: Decoder) throws {
            do {
                let decoded = try T(from: decoder)
                self = .success(decoded)
            } catch let error {
                self = .failure(error)
            }
        }
    }
    
    extension Throwable {
        var value: T? {
            switch self {
            case .failure(_):
                return nil
            case .success(let value):
                return value
            }
        }
    }
    
    我会选择使用
    enum
    包装类型(在
    Struct
    上),因为它可能有助于跟踪抛出的错误及其索引

    斯威夫特5

    为SWIFT 5考虑使用<代码> EnUM <代码> >

    struct Throwable<T: Decodable>: Decodable {
        let result: Result<T, Error>
    
        init(from decoder: Decoder) throws {
            result = Result(catching: { try T(from: decoder) })
        }
    }
    

    我提出了这个
    KeyedDecodingContainer.safelyDecodeArray
    ,它提供了一个简单的界面:

    extension KeyedDecodingContainer {
    
    /// The sole purpose of this `EmptyDecodable` is allowing decoder to skip an element that cannot be decoded.
    private struct EmptyDecodable: Decodable {}
    
    /// Return successfully decoded elements even if some of the element fails to decode.
    func safelyDecodeArray<T: Decodable>(of type: T.Type, forKey key: KeyedDecodingContainer.Key) -> [T] {
        guard var container = try? nestedUnkeyedContainer(forKey: key) else {
            return []
        }
        var elements = [T]()
        elements.reserveCapacity(container.count ?? 0)
        while !container.isAtEnd {
            /*
             Note:
             When decoding an element fails, the decoder does not move on the next element upon failure, so that we can retry the same element again
             by other means. However, this behavior potentially keeps `while !container.isAtEnd` looping forever, and Apple does not offer a `.skipFailable`
             decoder option yet. As a result, `catch` needs to manually skip the failed element by decoding it into an `EmptyDecodable` that always succeed.
             See the Swift ticket https://bugs.swift.org/browse/SR-5953.
             */
            do {
                elements.append(try container.decode(T.self))
            } catch {
                if let decodingError = error as? DecodingError {
                    Logger.error("\(#function): skipping one element: \(decodingError)")
                } else {
                    Logger.error("\(#function): skipping one element: \(error)")
                }
                _ = try? container.decode(EmptyDecodable.self) // skip the current element by decoding it into an empty `Decodable`
            }
        }
        return elements
    }
    }
    
    扩展键DecodingContainer{
    ///此“EmptyCodeable”的唯一用途是允许解码器跳过无法解码的元素。
    私有结构EmptyCodeable:可解码{}
    ///返回已成功解码的元素,即使某些元素未能解码。
    func safelyDecodeArray(类型:T.type,forKey:keyedDecodeContainer.key)->[T]{
    guard var container=try?nestedUnkeyedContainer(forKey:key)else{
    返回[]
    }
    变量元素=[T]()
    元素。保留容量(容器计数??0)
    while!container.isattend{
    /*
    注:
    当解码一个元素失败时,解码器在失败时不会移动到下一个元素,因此我们可以再次重试同一个元素
    然而,这种行为可能会让'while!container.isattend'永远循环,苹果也不会提供一个'.skipFailable'`
    解码器选项。因此,`catch`需要手动跳过失败的元素,将其解码为始终成功的`EmptyCodeable`。
    看到快捷票了吗https://bugs.swift.org/browse/SR-5953.
    */
    
    extension Throwable {
        var value: T? {
            switch self {
            case .failure(_):
                return nil
            case .success(let value):
                return value
            }
        }
    }
    
    struct Throwable<T: Decodable>: Decodable {
        let result: Result<T, Error>
    
        init(from decoder: Decoder) throws {
            result = Result(catching: { try T(from: decoder) })
        }
    }
    
    let products = throwables.compactMap { try? $0.result.get() }
    
    extension KeyedDecodingContainer {
    
    /// The sole purpose of this `EmptyDecodable` is allowing decoder to skip an element that cannot be decoded.
    private struct EmptyDecodable: Decodable {}
    
    /// Return successfully decoded elements even if some of the element fails to decode.
    func safelyDecodeArray<T: Decodable>(of type: T.Type, forKey key: KeyedDecodingContainer.Key) -> [T] {
        guard var container = try? nestedUnkeyedContainer(forKey: key) else {
            return []
        }
        var elements = [T]()
        elements.reserveCapacity(container.count ?? 0)
        while !container.isAtEnd {
            /*
             Note:
             When decoding an element fails, the decoder does not move on the next element upon failure, so that we can retry the same element again
             by other means. However, this behavior potentially keeps `while !container.isAtEnd` looping forever, and Apple does not offer a `.skipFailable`
             decoder option yet. As a result, `catch` needs to manually skip the failed element by decoding it into an `EmptyDecodable` that always succeed.
             See the Swift ticket https://bugs.swift.org/browse/SR-5953.
             */
            do {
                elements.append(try container.decode(T.self))
            } catch {
                if let decodingError = error as? DecodingError {
                    Logger.error("\(#function): skipping one element: \(decodingError)")
                } else {
                    Logger.error("\(#function): skipping one element: \(error)")
                }
                _ = try? container.decode(EmptyDecodable.self) // skip the current element by decoding it into an empty `Decodable`
            }
        }
        return elements
    }
    }
    
    let products = [GroceryProduct?]
    
    struct FailableCodableArray<Element : Codable> : Codable {
    
        var elements: [Element]
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let elements = try container.decode([FailableDecodable<Element>].self)
            self.elements = elements.compactMap { $0.wrapped }
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encode(elements)
        }
    }
    
    struct Person: Codable {
        var name: String
        var age: Int
        var description: String?
        var friendnamesArray:[String]?
    }
    
    struct Person: Codable {
        var name: String
        var age: Int
        var description: String?
        var friendnamesArray:[String?]?
    }
    
    private struct OptionalContainer<Base: Codable>: Codable {
        let base: Base?
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            base = try? container.decode(Base.self)
        }
    }
    
    private struct OptionalArray<Base: Codable>: Codable {
        let result: [Base]
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let tmp = try container.decode([OptionalContainer<Base>].self)
            result = tmp.compactMap { $0.base }
        }
    }
    
    extension Array where Element: Codable {
        init(from decoder: Decoder) throws {
            let optionalArray = try OptionalArray<Element>(from: decoder)
            self = optionalArray.result
        }
    }
    
    struct GroceryProduct: Decodable {
        var name: String
        var points: Int
        var description: String?
    }'
    
    'let groceryList = try JSONDecoder().decode(Array<GroceryProduct>.self, from: responseData)'
    
    class CompactDecodableArray<Element>: Decodable where Element: Decodable {
        private(set) var elements = [Element]()
        required init(from decoder: Decoder) throws {
            guard var unkeyedContainer = try? decoder.unkeyedContainer() else { return }
            while !unkeyedContainer.isAtEnd {
                if let value = try? unkeyedContainer.decode(Element.self) {
                    elements.append(value)
                } else {
                    unkeyedContainer.skip()
                }
            }
        }
    }
    
    // https://forums.swift.org/t/pitch-unkeyeddecodingcontainer-movenext-to-skip-items-in-deserialization/22151/17
    
    struct Empty: Decodable { }
    
    extension UnkeyedDecodingContainer {
        mutating func skip() { _ = try? decode(Empty.self) }
    }
    
    struct Model2: Decodable {
        let num: Int
        let str: String
    }
    
    struct Model: Decodable {
        let num: Int
        let str: String
        let array1: CompactDecodableArray<Int>
        let array2: CompactDecodableArray<Int>?
        let array4: CompactDecodableArray<Model2>
    }
    
    let dictionary: [String : Any] = ["num": 1, "str": "blablabla",
                                      "array1": [1,2,3],
                                      "array3": [1,nil,3],
                                      "array4": [["num": 1, "str": "a"], ["num": 2]]
    ]
    
    let data = try! JSONSerialization.data(withJSONObject: dictionary)
    let object = try JSONDecoder().decode(Model.self, from: data)
    print("1. \(object.array1.elements)")
    print("2. \(object.array2?.elements)")
    print("3. \(object.array4.elements)")
    
    1. [1, 2, 3]
    2. nil
    3. [__lldb_expr_25.Model2(num: 1, str: "a")]