arrays - tutorial - swiftyjson vs codable




如果單個元素解碼失敗,Swift JSONDecode解碼數組將失敗 (8)

@Hamish的答案很棒。 但是,您可以將 FailableCodableArray 減少為:

struct FailableCodableArray<Element : Codable> : Codable {

    var elements: [Element]

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let elements = try container.decode([FailableDecodable<Element>].self)
        self.elements = elements.compactMap { $0.wrapped }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(elements)
    }
}

在使用Swift4和Codable協議時,我遇到了以下問題 - 看起來沒有辦法允許 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" 鍵,而 GroceryProduct 結構中的 points 不是可選的。

問題是如何讓 JSONDecoder “跳過”無效對象?


一個更簡單的嘗試:為什麼不將點聲明為可選或使數組包含可選元素

let products = [GroceryProduct?]

不幸的是,Swift 4 API沒有用於 init(from: Decoder) 可用初始化程序 init(from: Decoder)

我看到的只有一個解決方案是實現自定義解碼,為可選字段提供默認值,並為可能的過濾器提供所需數據:

struct GroceryProduct: Codable {
    let name: String
    let points: Int?
    let description: String

    private enum CodingKeys: String, CodingKey {
        case name, points, description
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        points = try? container.decode(Int.self, forKey: .points)
        description = (try? container.decode(String.self, forKey: .description)) ?? "No description"
    }
}

// for test
let dict = [["name": "Banana", "points": 100], ["name": "Nut", "description": "Woof"]]
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
    let decoder = JSONDecoder()
    let result = try? decoder.decode([GroceryProduct].self, from: data)
    print("rawResult: \(result)")

    let clearedResult = result?.filter { $0.points != nil }
    print("clearedResult: \(clearedResult)")
}

問題是,當迭代容器時,container.currentIndex不會遞增,因此您可以嘗試使用其他類型再次解碼。

因為currentIndex是只讀的,所以解決方案是自己遞增它以成功解碼虛擬對象。 我使用@Hamish解決方案,並使用自定義init編寫了一個包裝器。

這個問題是當前的Swift錯誤: https://bugs.swift.org/browse/SR-5953https://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)

我想出了這個 KeyedDecodingContainer.safelyDecodeArray ,它提供了一個簡單的接口:

extension KeyedDecodingContainer {

/// The sole purpose of this `EmptyDecodable` is allowing decoder to skip an element that cannot be decoded.
private struct EmptyDecodable: Decodable {}

/// Return successfully decoded elements even if some of the element fails to decode.
func safelyDecodeArray<T: Decodable>(of type: T.Type, forKey key: KeyedDecodingContainer.Key) -> [T] {
    guard var container = try? nestedUnkeyedContainer(forKey: key) else {
        return []
    }
    var elements = [T]()
    elements.reserveCapacity(container.count ?? 0)
    while !container.isAtEnd {
        /*
         Note:
         When decoding an element fails, the decoder does not move on the next element upon failure, so that we can retry the same element again
         by other means. However, this behavior potentially keeps `while !container.isAtEnd` looping forever, and Apple does not offer a `.skipFailable`
         decoder option yet. As a result, `catch` needs to manually skip the failed element by decoding it into an `EmptyDecodable` that always succeed.
         See the Swift ticket https://bugs.swift.org/browse/SR-5953.
         */
        do {
            elements.append(try container.decode(T.self))
        } catch {
            if let decodingError = error as? DecodingError {
                Logger.error("\(#function): skipping one element: \(decodingError)")
            } else {
                Logger.error("\(#function): skipping one element: \(error)")
            }
            _ = try? container.decode(EmptyDecodable.self) // skip the current element by decoding it into an empty `Decodable`
        }
    }
    return elements
}
}

可能是無限循環 while !container.isAtEnd 是一個問題,它通過使用 EmptyDecodableEmptyDecodable


我最近遇到了類似的問題,但略有不同。

struct Person: Codable {
    var name: String
    var age: Int
    var description: String?
    var friendnamesArray:[String]?
}

在這種情況下,如果 friendnamesArray 一個元素為nil,則解碼時整個對象為nil。

處理這種邊緣情況的正確方法是將字符串數組 [String] 聲明為可選字符串數組 [String?] ,如下所示,

struct Person: Codable {
    var name: String
    var age: Int
    var description: String?
    var friendnamesArray:[String?]?
}

我遇到了同樣的問題,發現沒有一個回復令人滿意。

我有以下結構:

public struct OfferResponse {
public private(set) var offers: [Offer]

public init(data: Data) throws {
    let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: [Any]]
    guard let offersDataArray = json?["Offers"] else {
        throw NSError(domain: "unexpected JSON structure for \(type(of: self))", code: 36, userInfo: nil)
    }
    guard let firstOfferData = offersDataArray.first else {
        throw NSError(domain: "emptyArray in JSON structure for \(type(of: self))", code: 36, userInfo: nil)
    }
    let decoder = JSONDecoder()
    offers = try decoder.decode([Offer].self, from: JSONSerialization.data(withJSONObject: firstOfferData, options: .prettyPrinted))
}

在某一點上,後端返回了元素的不良內容。 我這樣解決了:

    offers = []
    for offerData in offersDataArray {
        if let offer = try? decoder.decode(Offer.self, from: JSONSerialization.data(withJSONObject: offerData, options: .prettyPrinted)) {
            offers.append(offer)
        }            

有兩種選擇:

  1. 聲明結構的所有成員都是可選的,其鍵可能會丟失

    struct GroceryProduct: Codable {
        var name: String
        var points : Int?
        var description: String?
    }
  2. 編寫自定義初始值設定項以在 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) ?? ""
        }
    }






codable