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

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

和一个可编码的 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)

结果products为空.这是意料之中的,因为JSON中的第二个对象没有"points"键,而pointsGroceryProduct struct 中不是可选的.

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

推荐答案

一种 Select 是使用试图解码给定值的包装器类型;如果不成功,则存储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)
    }
}

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

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

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

然后,您将解码为:

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

Json相关问答推荐

您可以使用Jolt对JSON执行OR条件吗

从Razor Pages的AJAX Json呈现DataTables问题.Net GET

如何用JQ更改空/布尔/数字的 colored颜色 ?

使用Jolt v.0.1.1向每个json元素添加一个键-值对

JSON API返回多个数组,需要帮助拼合数据以存储在SQL Server数据库表中

报告重复的对象键

是否可以在有条件的情况下将 json 对象转换为 JOLT 中的数组?

使用 JQ 从文件中删除重复的 JSON 块

Microsoft GRAPH 查询使用端点 /deviceManagement/deviceHealthScripts 列出了一种不熟悉的检测脚本内容格式

hook到 Decodable.init() 以获得未指定的键?

如何判断 Json 对象中是否存在键并获取其值

如何使用法语口音对数组进行 json_encode?

如何对使用转换器的 Grails 服务进行单元测试?

可以通过 POST 使用 EventSource 传递参数的服务器发送事件 (SSE)

将带有数据和文件的 JSON 发布到 Web Api - jQuery / MVC

消息通知产生此内容无法显示

如何访问 JSON 对象数组的第一个元素?

[__NSCFNumber 长度]:发送到实例 UITableView 的无法识别的 Select 器

如何在spark 上将json字符串转换为数据帧

将多个值存储在json中的单个键中