arrays - स्विफ्ट JSONDecode डिकोडिंग सरणियों में विफल रहता है यदि एकल तत्व डिकोडिंग विफल हो जाता है




swift swift4 (8)

स्विफ्ट 4 और कोडेबल प्रोटोकॉल का उपयोग करते समय मुझे निम्नलिखित समस्या मिली - ऐसा लग रहा है कि JSONDecoder को तत्वों को एक सरणी में छोड़ने की अनुमति देने का कोई तरीका नहीं है। उदाहरण के लिए, मेरे पास निम्नलिखित JSON है:

[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]

और एक कोडेबल संरचना:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

इस जशन को डिकोड करते समय

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)

परिणामी products खाली है। यह उम्मीद की जा रही है, इस तथ्य के कारण कि JSON में दूसरी वस्तु में कोई "points" कुंजी नहीं है, जबकि GroceryProduct संरचना में points वैकल्पिक नहीं हैं।

प्रश्न यह है कि मैं JSONDecoder को अमान्य ऑब्जेक्ट को "स्किप" करने की अनुमति कैसे दे सकता हूं?


@ हामिश का जवाब बहुत अच्छा है। हालाँकि, आप 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)
    }
}

Ive विस्तार का उपयोग करने के लिए कुछ संशोधनों के साथ @ सोफी-स्विकज़ समाधान डाल दिया

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)
    }
}

ऐसे ही पुकारो

init(from decoder: Decoder) throws {

    let container = try decoder.container(keyedBy: CodingKeys.self)

    self.items = try container.decodeArray(ItemType.self, forKey: . items)
}

ऊपर के उदाहरण के लिए:

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)

दुर्भाग्य से स्विफ्ट 4 एपीआई में इनिट 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)")
}

दो विकल्प हैं:

  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) ?? ""
        }
    }

मुझे एक ही समस्या का सामना करना पड़ा और कोई भी जवाब संतोषजनक नहीं मिला।

मेरे पास निम्न संरचना थी:

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)
        }            

मेरे पास हाल ही में एक समान मुद्दा था, लेकिन थोड़ा अलग था।

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

इस स्थिति में, अगर friendnamesArray में एक तत्व शून्य है, तो डिकोडिंग करते समय पूरी वस्तु शून्य होती है।

और इस किनारे के मामले को संभालने का सही तरीका स्ट्रिंग स्ट्रिंग [String] को वैकल्पिक स्ट्रिंग्स [String?] रूप में घोषित करना है, नीचे [String?]

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

मैं एक नया प्रकार 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)
        }
    }
}

GroceryProduct के GroceryProduct (या किसी अन्य Collection ) के एक सरणी को GroceryProduct :

let decoder = JSONDecoder()
let throwables = try decoder.decode([Throwable<GroceryProduct>].self, from: json)
let products = throwables.compactMap { $0.value }

जहां value एक गणना की गई संपत्ति है जो Throwable पर एक्सटेंशन में पेश की गई है:

extension Throwable {
    var value: T? {
        switch self {
        case .failure(_):
            return nil
        case .success(let value):
            return value
        }
    }
}

मैं एक enum रैपर प्रकार (एक Struct से अधिक) का उपयोग करने का विकल्प Struct क्योंकि यह उन त्रुटियों का ट्रैक रखने के लिए उपयोगी हो सकता है जो उनके सूचकांकों के साथ-साथ फेंके गए हैं।


समस्या यह है कि जब एक कंटेनर पर पुनरावृत्ति होती है, तो कंटेनर.currentIndex में वृद्धि नहीं की जाती है ताकि आप एक अलग प्रकार के साथ फिर से डिकोड करने का प्रयास कर सकें।

क्योंकि करंटइंडेक्स केवल पढ़ा जाता है, एक समाधान यह है कि यह अपने आप बढ़े और सफलतापूर्वक एक डमी को डिकोड करे। मैंने @ हैमिश समाधान लिया, और एक कस्टम इनिट के साथ एक आवरण लिखा।

यह समस्या एक वर्तमान स्विफ्ट बग: 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)




codable