Khr*_*riy 86 arrays json swift swift4 codable
在使用Swift4和Codable协议时,我遇到了以下问题 - 看起来没有办法允许JSONDecoder跳过数组中的元素.例如,我有以下JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
Run Code Online (Sandbox Code Playgroud)
还有一个Codable结构:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Run Code Online (Sandbox Code Playgroud)
解码这个json时
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Run Code Online (Sandbox Code Playgroud)
结果products是空的.这是可以预料到的,因为JSON中的第二个对象没有"points"键,而struct中points不是可选的GroceryProduct.
问题是如何允许JSONDecoder"跳过"无效对象?
Ham*_*ish 93
一种选择是使用尝试解码给定值的包装器类型; 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)
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以解码这些数组,并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.")
// )
// ]
Run Code Online (Sandbox Code Playgroud)
然后我们.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)
}
}
Run Code Online (Sandbox Code Playgroud)
然后你会解码为:
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.")
// )
// ]
Run Code Online (Sandbox Code Playgroud)
Sop*_*icz 23
问题是,当迭代容器时,container.currentIndex不会递增,因此您可以尝试使用其他类型再次解码.
因为currentIndex是只读的,所以解决方案是自己递增它以成功解码虚拟对象.我使用@Hamish解决方案,并使用自定义init编写了一个包装器.
这个问题是当前的Swift错误:https://bugs.swift.org/browse/SR-5953
此处发布的解决方案是其中一条评论中的解决方法.我喜欢这个选项,因为我在网络客户端上以相同的方式解析了一堆模型,我希望解决方案对其中一个对象是本地的.也就是说,我仍然希望其他人被丢弃.
我解释我的github上更好https://github.com/phynet/Lossy-array-decode-swift4
import Foundation
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
private struct DummyCodable: Codable {}
struct Groceries: Codable
{
var groceries: [GroceryProduct]
init(from decoder: Decoder) throws {
var groceries = [GroceryProduct]()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(GroceryProduct.self) {
groceries.append(route)
} else {
_ = try? container.decode(DummyCodable.self) // <-- TRICK
}
}
self.groceries = groceries
}
}
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)
Run Code Online (Sandbox Code Playgroud)
cfe*_*gie 23
我会创建一个新类型Throwable,它可以包装符合Decodable以下类型的任何类型:
enum Throwable<T: Decodable>: Decodable {
case success(T)
case failure(Error)
init(from decoder: Decoder) throws {
do {
let decoded = try T(from: decoder)
self = .success(decoded)
} catch let error {
self = .failure(error)
}
}
}
Run Code Online (Sandbox Code Playgroud)
用于解码GroceryProduct(或任何其他Collection)数组:
let decoder = JSONDecoder()
let throwables = try decoder.decode([Throwable<GroceryProduct>].self, from: json)
let products = throwables.compactMap { $0.value }
Run Code Online (Sandbox Code Playgroud)
where value是在扩展中引入的计算属性Throwable:
extension Throwable {
var value: T? {
switch self {
case .failure(_):
return nil
case .success(let value):
return value
}
}
}
Run Code Online (Sandbox Code Playgroud)
我会选择使用enum包装器类型(通过a Struct),因为跟踪抛出的错误及其索引可能很有用.
vad*_*ian 17
有两种选择:
声明结构的所有成员都是可选的,其键可能会丢失
struct GroceryProduct: Codable {
var name: String
var points : Int?
var description: String?
}
Run Code Online (Sandbox Code Playgroud)编写自定义初始化程序以在nil案例中指定默认值.
struct GroceryProduct: Codable {
var name: String
var points : Int
var description: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
points = try values.decodeIfPresent(Int.self, forKey: .points) ?? 0
description = try values.decodeIfPresent(String.self, forKey: .description) ?? ""
}
}
Run Code Online (Sandbox Code Playgroud)rra*_*ael 17
Swift 5.1 使用属性包装器实现的解决方案:
@propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
var wrappedValue: [Value] = []
private struct _None: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let decoded = try? container.decode(Value.self) {
wrappedValue.append(decoded)
}
else {
// item is silently ignored.
try? container.decode(_None.self)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后是用法:
let json = """
{
"products": [
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
}
""".data(using: .utf8)!
struct GroceryProduct: Decodable {
var name: String
var points: Int
var description: String?
}
struct ProductResponse: Decodable {
@IgnoreFailure
var products: [GroceryProduct]
}
let response = try! JSONDecoder().decode(ProductResponse.self, from: json)
print(response.products) // Only contains banana.
Run Code Online (Sandbox Code Playgroud)
注意:只有当响应可以包装在结构中(即:不是顶级数组)时,属性包装器才有效。在这种情况下,您仍然可以手动包装它(使用类型别名以获得更好的可读性):
typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>
let response = try! JSONDecoder().decode(ArrayIgnoringFailure<GroceryProduct>.self, from: json)
print(response.wrappedValue) // Only contains banana.
Run Code Online (Sandbox Code Playgroud)
我已经将@ sophy-swicz解决方案(经过一些修改)放入一个易于使用的扩展中
fileprivate struct DummyCodable: Codable {}
extension UnkeyedDecodingContainer {
public mutating func decodeArray<T>(_ type: T.Type) throws -> [T] where T : Decodable {
var array = [T]()
while !self.isAtEnd {
do {
let item = try self.decode(T.self)
array.append(item)
} catch let error {
print("error: \(error)")
// hack to increment currentIndex
_ = try self.decode(DummyCodable.self)
}
}
return array
}
}
extension KeyedDecodingContainerProtocol {
public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
var unkeyedContainer = try self.nestedUnkeyedContainer(forKey: key)
return try unkeyedContainer.decodeArray(type)
}
}
Run Code Online (Sandbox Code Playgroud)
就这样称呼它
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.items = try container.decodeArray(ItemType.self, forKey: . items)
}
Run Code Online (Sandbox Code Playgroud)
对于上面的例子:
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
struct Groceries: Codable
{
var groceries: [GroceryProduct]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
groceries = try container.decodeArray(GroceryProduct.self)
}
}
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)
Run Code Online (Sandbox Code Playgroud)
受到以前答案的启发,我在 Result 枚举扩展中进行了解码。
你怎么看待这件事?
extension Result: Decodable where Success: Decodable, Failure == DecodingError {
public init(from decoder: Decoder) throws {
let container: SingleValueDecodingContainer = try decoder.singleValueContainer()
do {
self = .success(try container.decode(Success.self))
} catch {
if let decodingError = error as? DecodingError {
self = .failure(decodingError)
} else {
self = .failure(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: error.localizedDescription)))
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
let listResult = try? JSONDecoder().decode([Result<SomeObject, DecodingError>].self, from: ##YOUR DATA##)
let list: [SomeObject] = listResult.compactMap {try? $0.get()}
Run Code Online (Sandbox Code Playgroud)
小智 5
相反,您也可以这样做:
struct GroceryProduct: Decodable {
var name: String
var points: Int
var description: String?
}'
Run Code Online (Sandbox Code Playgroud)
然后在获取它的同时:
'let groceryList = try JSONDecoder().decode(Array<GroceryProduct>.self, from: responseData)'
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
41667 次 |
| 最近记录: |