Một tùy chọn là sử dụng loại trình bao bọc cố gắng giải mã một giá trị nhất định; lưu trữ nil
nếu không thành công:
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)
}
}
Sau đó, chúng tôi có thể giải mã một mảng trong số này, với việc bạn GroceryProduct
điền vào Base
trình giữ chỗ:
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.")
// )
// ]
Sau đó, chúng tôi đang sử dụng .compactMap { $0.base }
để lọc ra nil
các phần tử (những phần tử gây ra lỗi khi giải mã).
Điều này sẽ tạo ra một mảng trung gian [FailableDecodable<GroceryProduct>]
, không phải là một vấn đề; tuy nhiên nếu bạn muốn tránh nó, bạn luôn có thể tạo một loại trình bao bọc khác để giải mã và mở từng phần tử từ một vùng chứa không có khóa:
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)
}
}
Sau đó, bạn sẽ giải mã thành:
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.")
// )
// ]