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/46344963/

相关文章:

JavaScript promise 不与 resolve() 一起工作

python - 使用 python 生成具有限制和一定长度的给定数字的随机列表

java - 如何在pojo中使用jpa或hibernate映射json字段?

ios - PerformSegueWithIdentifier 触发,但不显示

ios - 未在 subview 上调用 touchesBegan()

php - 显示数组中所有元素的数据类型

arrays - 聚合连接 jsonb 数组

c# - 识别一个属性的值是一个数组

android - 当数据是字符串而不是对象时,如何在 android 中使用解析相同的 json 响应?

swift - 如何使用Cloud Firestore中的geopoint设置tableview以将商店放置在离我当前位置最近的位置?