在使用 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"
键,而 points
在 GroceryProduct
中不是可选的> 结构。
问题是如何让 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/