如果单个元素解码失败,Swift JSONDecode 解码数组就会失败 [英] Swift JSONDecode decoding arrays fails if single element decoding fails

查看:98
本文介绍了如果单个元素解码失败,Swift JSONDecode 解码数组就会失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

<预><代码>[{"name": "香蕉",点数":200,"description": "一种生长在厄瓜多尔的香蕉."},{名称":橙色"}]

还有一个 Codable 结构:

struct GroceryProduct: Codable {变量名:字符串变量点:Int变量描述:字符串?}

解码这个json时

让解码器 = JSONDecoder()让产品 = 尝试decoder.decode([GroceryProduct].self, from: json)

结果 products 为空.这是可以预料的,因为 JSON 中的第二个对象没有 "points" 键,而 pointsGroceryProduct 结构.

问题是如何让 JSONDecoder跳过"无效对象?

解决方案

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

struct FailableDecodable:可解码{让基地:基地?init(来自解码器:解码器)抛出{让容器 = 尝试decoder.singleValueContainer()self.base = 试试?container.decode(Base.self)}}

然后我们可以解码这些数组,并用您的 GroceryProduct 填充 Base 占位符:

导入基础让 json = """[{"name": "香蕉",点数":200,"description": "一种生长在厄瓜多尔的香蕉."},{名称":橙色"}]""".data(使用:.utf8)!struct GroceryProduct : Codable {变量名:字符串变量点:Int变量描述:字符串?}让产品 = 尝试 JSONDecoder().decode([FailableDecodable<GroceryProduct>].self, from: json).compactMap { $0.base }//Swift 4.0 中的 .flatMap印刷(产品)//[//杂货产品(//名称:香蕉",点数:200,//描述:可选(厄瓜多尔种植的香蕉.")//)//]

然后我们使用 .compactMap { $0.base } 来过滤掉 nil 元素(那些在解码时抛出错误的元素).

这将创建一个 [FailableDecodable] 的中间数组,这应该不是问题;然而,如果你想避免它,你总是可以创建另一种包装器类型来解码和解开一个未加密的容器中的每个元素:

struct FailableCodableArray<元素:可编码>:可编码{var 元素:[元素]init(来自解码器:解码器)抛出{var container = 尝试decoder.unkeyedContainer()var 元素 = [元素]()如果让计数 = container.count {element.reserveCapacity(count)}而 !container.isAtEnd {如果让元素 = 尝试容器.decode(FailableDecodable.self).base {元素.附加(元素)}}self.elements = 元素}func 编码(编码器:编码器)抛出 {var 容器 = 编码器.singleValueContainer()尝试 container.encode(elements)}}

然后您将解码为:

let products = 尝试 JSONDecoder().decode(FailableCodableArray.self, from: json).elements印刷(产品)//[//杂货产品(//名称:香蕉",点数:200,//描述:可选(厄瓜多尔种植的香蕉.")//)//]

While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder to skip elements in an array. For example, I have the following JSON:

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

And a Codable struct:

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

When decoding this json

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

Resulting products is empty. Which is to be expected, due to the fact that the second object in JSON has no "points" key, while points is not optional in GroceryProduct struct.

Question is how can I allow JSONDecoder to "skip" invalid object?

解决方案

One option is to use a wrapper type that attempts to decode a given value; storing nil if unsuccessful:

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)
    }
}

We can then decode an array of these, with your GroceryProduct filling in the Base placeholder:

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.")
//    )
// ]

We're then using .compactMap { $0.base } to filter out nil elements (those that threw an error on decoding).

This will create an intermediate array of [FailableDecodable<GroceryProduct>], which shouldn't be an issue; however if you wish to avoid it, you could always create another wrapper type that decodes and unwraps each element from an unkeyed container:

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)
    }
}

You would then decode as:

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.")
//    )
// ]

这篇关于如果单个元素解码失败,Swift JSONDecode 解码数组就会失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆