একক উপাদান ডিকোডিং ব্যর্থ হলে সুইফট জেএসএনডিকোড ডিকোডিং অ্যারে ব্যর্থ


116

সুইফট 4 এবং কোডেবল প্রোটোকলগুলি ব্যবহার করার সময় আমি নিম্নলিখিত সমস্যাটি পেয়েছি - দেখে মনে হচ্ছে JSONDecoderঅ্যারেতে উপাদানগুলি এড়িয়ে যাওয়ার কোনও উপায় নেই । উদাহরণস্বরূপ, আমার কাছে নিম্নলিখিত জেএসএন রয়েছে:

[
    {
        "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"কী নেই, যদিও pointsএটি alচ্ছিক নয় সে কারণে কোনটি আশা করা যায়GroceryProduct স্ট্রাক্টে alচ্ছিক ।

প্রশ্নটি কীভাবে আমি JSONDecoderঅবৈধ অবজেক্টটিকে "এড়িয়ে যেতে" দেব ?


আমরা অবৈধ অবজেক্টগুলি এড়াতে পারি না তবে শূন্য থাকলে আপনি ডিফল্ট মান নির্ধারণ করতে পারেন।
ভিনি অ্যাপ

1
কেন pointsশুধু declaredচ্ছিক ঘোষণা করা যাবে না ?
NRitH

উত্তর:


115

একটি বিকল্প হ'ল একটি মোড়কের ধরণ ব্যবহার করা যা প্রদত্ত মানকে ডিকোড করার চেষ্টা করে; 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)
    }
}

এর পরে আমরা স্থানধারকটি 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.")
//    )
// ]

আমরা তখন .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)
    }
}

তারপরে আপনি এই হিসাবে ডিকোড করবেন:

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.")
//    )
// ]

1
যদি বেস অবজেক্টটি অ্যারে না হয় তবে এতে একটি থাকে? {"পণ্যগুলি": [{"নাম": "কলা" ...}, ...]}
লুডভিগ্রিকসন

2
@ludvigeriksson আপনি কেবল সেই কাঠামোর মধ্যেই ডিকোডিং সম্পাদন করতে চান তবে উদাহরণস্বরূপ: gist.github.com/hamishknight/c6d270f7298e4db9e787aecb5b98bcae
হামিশ

1
সুইফটের কোডেবল সহজ ছিল, এখন অবধি .. এটাকে সহজতর করা যায় না?
জনি

@ হামিশ আমি এই লাইনের জন্য কোনও ত্রুটি দেখছি না। এখানে যদি কোনও ত্রুটি নিক্ষেপ করা হয় তবে কী হবেvar container = try decoder.unkeyedContainer()
বাইবসি

@ বিবিসিটি এটির দেহের মধ্যে রয়েছে init(from:) throws, তাই সুইফট স্বয়ংক্রিয়ভাবে ত্রুটিটি কলারের কাছে ফিরে প্রচার করবে (এক্ষেত্রে ডিকোডার, যা এটি JSONDecoder.decode(_:from:)কলটিতে আবার প্রচার করবে )।
হামিশ

33

আমি একটি নতুন ধরণের তৈরি করব 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(বা অন্য কোনও Collection) এর অ্যারে ডিকোড করার জন্য :

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 ) কারণ নিক্ষেপ করা ত্রুটিগুলির পাশাপাশি তাদের সূচকগুলিও নজর রাখতে এটি কার্যকর হতে পারে।

সুইফট 5

সুইফট 5 এর জন্য উদাঃ ব্যবহার করে বিবেচনা করুনResult enum

struct Throwable<T: Decodable>: Decodable {
    let result: Result<T, Error>

    init(from decoder: Decoder) throws {
        result = Result(catching: { try T(from: decoder) })
    }
}

ডিকোডড মানটি মোড়ক get()করতে resultসম্পত্তিটিতে এই পদ্ধতিটি ব্যবহার করুন :

let products = throwables.compactMap { try? $0.result.get() }

আমি এই উত্তরটি পছন্দ করি কারণ আমাকে কোনও রীতিনীতি লেখার বিষয়ে চিন্তা করতে হবে নাinit
মিহাই ফ্রাতু

এটিই আমি সমাধান খুঁজছিলাম। এটা এত পরিষ্কার এবং সোজা। এই জন্য আপনাকে ধন্যবাদ!
Naturaln0va

24

সমস্যাটি হ'ল যখন কোনও ধারকটির উপরে পুনরাবৃত্তি হয়, তখন ধারকটি কর্নার ইনডেক্স বর্ধিত হয় না তাই আপনি আবার কোনও অন্য ধরণের সাথে ডিকোড করার চেষ্টা করতে পারেন।

যেহেতু বর্তমানআইডেক্সটি কেবল পঠনযোগ্য, একটি সমাধান হ'ল এটি নিজেকে বাড়িয়ে তোলা সাফল্যের সাথে একটি ডামি সাফল্যের সাথে ডিকোড করে। আমি @ হামিশ সলিউশন নিয়েছি এবং একটি কাস্টম ডিআইডি সহ একটি মোড়ক লিখেছিলাম

এই সমস্যাটি একটি বর্তমান সুইফট বাগ: https://bugs.swift.org/browse/SR-5953

এখানে পোস্ট করা সমাধানটি একটি মন্তব্যের মধ্যে একান্ত কার্যকর। আমি এই বিকল্পটি পছন্দ করি কারণ আমি একটি নেটওয়ার্ক ক্লায়েন্টে একইভাবে একগুচ্ছ মডেলগুলি পার্স করছি, এবং আমি সমাধানটি কোনও একটির কাছে স্থানীয় হতে চেয়েছিলাম। এটি হ'ল আমি এখনও চাই অন্যরা ফেলে দেওয়া হোক।

আমি আমার গিথুব 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)

1
একটি পরিবর্তনের পরিবর্তে if/elseআমি do/catchwhile
ফ্রেজার

2
এই উত্তরে সুইফট বাগ ট্র্যাকার উল্লেখ করা হয়েছে এবং এতে সহজতম অতিরিক্ত কাঠামো রয়েছে (জেনারিকস নেই!) তাই আমি মনে করি এটি গ্রহণযোগ্য হওয়া উচিত।
আল্পার

2
এটি গ্রহণযোগ্য উত্তর হওয়া উচিত। আপনার ডেটা মডেলকে কলুষিত করে এমন কোনও উত্তর একটি অগ্রহণযোগ্য ট্রেড অফ ইমো।
জো সুজনিক

21

দুটি বিকল্প রয়েছে:

  1. কাঠামোর সমস্ত সদস্যকে alচ্ছিক হিসাবে ঘোষণা করুন যার কীগুলি অনুপস্থিত হতে পারে

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

5
বদলে try?দিয়ে decodeএটি ব্যবহার করতে ভাল tryসঙ্গে decodeIfPresentদ্বিতীয় বিকল্প হবে। আমাদের কোনও ডিফল্ট মান সেট করতে হবে যদি কোনও কী থাকে না, কোনও ডিকোডিং ব্যর্থতার ক্ষেত্রে নয়, যেমন কী উপস্থিত থাকে তবে টাইপটি ভুল।
ব্যবহারকারী 28434

আরে @vedia আপনি কি কাস্টম ইনিশিয়ালাইজার জড়িত ক্ষেত্রে ডিফল্ট মান নির্ধারণের জন্য জড়িত অন্য কোনও প্রশ্ন জানেন না? আমার কাছে একটি চাবি রয়েছে যা অন্তর্নিহিত তবে কখনও কখনও JSON এ একটি স্ট্রিং হয়ে যায় তাই আপনি উপরে যা বলেছিলেন তা করার চেষ্টা করেছি deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000তাই যদি এটি ব্যর্থ হয় তবে এটি কেবল 0000 এ দেবে তবে এটি এখনও ব্যর্থ হয়।
মার্থেলি

এই ক্ষেত্রেটি decodeIfPresentভুল APIকারণ কীটি বিদ্যমান। অন্য একটি do - catchব্লক ব্যবহার করুন । ডিকোড String, যদি কোনও ত্রুটি দেখা দেয় তবে ডিকোড করুনInt
ভাদিয়ান

13

সম্পত্তি র‌্যাপার ব্যবহার করে সুইফট 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)
            }
        }
    }
}

এবং তারপরে ব্যবহার:

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.

দ্রষ্টব্য: সম্পত্তির মোড়কের জিনিসগুলি কেবল তখনই কাজ করবে যদি প্রতিক্রিয়াটি কোনও স্ট্রাক্টে আবৃত করা যায় (যেমন: শীর্ষ স্তরের অ্যারে নয়)। সেক্ষেত্রে আপনি এখনও এটি ম্যানুয়ালি মোড়ানো করতে পারেন (আরও ভাল পাঠযোগ্যতার জন্য টাইপালিয়াস সহ):

typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>

let response = try! JSONDecoder().decode(ArrayIgnoringFailure<GroceryProduct>.self, from: json)
print(response.wrappedValue) // Only contains banana.

7

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)

Ive এই সমাধানটি একটি এক্সটেনশনে গিটিয়েছে github.com/IdleHandsApps/SafeDecoder
ফ্রেজার

3

দুর্ভাগ্যক্রমে সুইফট 4 এপিআই-এর জন্য অনুপযুক্ত আরম্ভকারী নেই init(from: Decoder)

আমি দেখতে পাই কেবলমাত্র একটি সমাধান হ'ল কাস্টম ডিকোডিং বাস্তবায়ন করা, dataচ্ছিক ক্ষেত্রগুলির জন্য ডিফল্ট মান দেওয়া এবং প্রয়োজনীয় ডেটা সহ সম্ভাব্য ফিল্টার:

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

2

আমার সম্প্রতি একটি অনুরূপ সমস্যা ছিল, তবে কিছুটা আলাদা।

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

এই ক্ষেত্রে, যদি উপাদানগুলির মধ্যে একটি হয় friendnamesArray শূন্য হয়, ডিকোডিংয়ের সময় পুরো বস্তুটি শূন্য হয়।

এবং এই প্রান্তের কেসটি হ্যান্ডেল করার সঠিক উপায় হ'ল স্ট্রিং অ্যারেটিকে below [String]চ্ছিক স্ট্রিংগুলির অ্যারে [String?]হিসাবে নীচে হিসাবে ঘোষণা করা ,

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

2

মামলার জন্য আমি @ হামিশের উন্নতি করেছি, আপনি সমস্ত অ্যারের জন্য এই আচরণ চান:

private struct OptionalContainer<Base: Codable>: Codable {
    let base: Base?
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        base = try? container.decode(Base.self)
    }
}

private struct OptionalArray<Base: Codable>: Codable {
    let result: [Base]
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let tmp = try container.decode([OptionalContainer<Base>].self)
        result = tmp.compactMap { $0.base }
    }
}

extension Array where Element: Codable {
    init(from decoder: Decoder) throws {
        let optionalArray = try OptionalArray<Element>(from: decoder)
        self = optionalArray.result
    }
}

1

@ হামিশের উত্তর দুর্দান্ত। তবে, আপনি এটি হ্রাস করতে পারেন 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)
    }
}

1

পরিবর্তে, আপনি এটির মতোও করতে পারেন:

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

এবং তারপরে এটি পাওয়ার সময়:

'let groceryList = try JSONDecoder().decode(Array<GroceryProduct>.self, from: responseData)'

0

আমি এটি নিয়ে এসেছি 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একটি উদ্বেগ এবং এটি ব্যবহার করে সম্বোধন করা EmptyDecodable


0

অনেক সহজ প্রচেষ্টা: আপনি পয়েন্টগুলি alচ্ছিক হিসাবে ঘোষণা করবেন না বা অ্যারেতে optionচ্ছিক উপাদানগুলি কেন তৈরি করবেন না

let products = [GroceryProduct?]
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.