如果单元素解码失败,Swift JSONDecode解码数组也会失败 [英] Swift JSONDecode decoding arrays fails if single element decoding fails
问题描述
在使用Swift4和Codable协议时,我遇到了以下问题-似乎没有办法允许JSONDecoder
跳过数组中的元素.
例如,我具有以下JSON:
While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder
to skip elements in an array.
For example, I have the following 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
结构中不是可选的.
Resulting products
is empty. Which is to be expected, due to the fact that the second object in JSON has no "points"
key, while points
is not optional in GroceryProduct
struct.
问题是如何允许JSONDecoder
跳过"无效对象?
Question is how can I allow JSONDecoder
to "skip" invalid object?
推荐答案
一种选择是使用包装类型尝试对给定值进行解码;如果失败,则存储nil
:
One option is to use a wrapper type that attempts to decode a given value; storing nil
if unsuccessful:
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
占位符:
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
元素(在解码时抛出错误的元素).
We're then using .compactMap { $0.base }
to filter out nil
elements (those that threw an error on decoding).
这将创建一个[FailableDecodable<GroceryProduct>]
的中间数组,这应该不成问题;但是,如果您希望避免这种情况,则可以始终创建另一个包装器类型,该类型可以解码和取消包装来自无键容器的每个元素:
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.")
// )
// ]
这篇关于如果单元素解码失败,Swift JSONDecode解码数组也会失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!