arrays - 如果单个元素解码失败,Swift JSONDecode 解码数组将失败

标签 arrays json swift swift4 codable

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

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

还有一个 Codable 结构:

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" 键,而 pointsGroceryProduct 中不是可选的> 结构。

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

最佳答案

一种选择是使用尝试解码给定值的包装器类型;存储 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)
    }
}

然后我们可以用你的 GroceryProduct 解码这些数组填写Base占位符:

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

关于arrays - 如果单个元素解码失败,Swift JSONDecode 解码数组将失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47536670/

相关文章:

c# - 为什么用内联初始化创建数组这么慢?

arrays - 动态 Haxe 迭代

java - 带有 Android 的 Azure 移动服务

android - 使用 Retrofit 解析动态 key Json 字符串

ios - Swift:按字母顺序对数组进行排序

PHP implode() 用 "array"替换所有数组值

java - ArrayList 包含来自另一个 ArrayList 的一个或多个实体

PHP Laravel-Eloquent在调用方法json时随机返回 bool 字段,有时返回0 1,其他返回true。

ios - 从枚举设置 UISegmentedControl 的标题(内置 IB,不是代码)

ios - 观察多个controlEvent导致 `Reentrancy anomaly was detected.`警告信息