在使用Swift4和Codable协议时,我遇到了以下问题-似乎没有办法允许JSONDecoder跳过数组中的元素。例如,我有以下JSON:
JSONDecoder
[ { "name": "Banana", "points": 200, "description": "A banana grown in Ecuador." }, { "name": "Orange" } ]
和一个可 编码的 结构:
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在GroceryProductstruct中不是可选的。
products
"points"
points
GroceryProduct
问题是如何允许JSONDecoder“跳过”无效对象?
一种选择是使用包装器类型,尝试对给定值进行解码。nil如果不成功,则存储:
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占位符中填写以下内容:
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元素(那些在解码时引发错误的元素)。
.compactMap { $0.base }
这将创建的中间数组[FailableDecodable<GroceryProduct>],这不应该成为问题;但是,如果您希望避免这种情况,可以始终创建另一个包装器类型,该类型将解码和解包来自无键容器的每个元素:
[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.") // ) // ]