সুইফট 4-এ জেএসএনডিকোডার দিয়ে, অনুপস্থিত কীগুলি alচ্ছিক বৈশিষ্ট্য হওয়ার পরিবর্তে কোনও ডিফল্ট মান ব্যবহার করতে পারে?


114

সুইফট 4 নতুন Codableপ্রোটোকল যুক্ত করেছে । আমি যখন ব্যবহার JSONDecoderকরি তখন মনে হয় আমার Codableশ্রেণীর সমস্ত অ-alচ্ছিক বৈশিষ্ট্যগুলির JSON- এ কী থাকতে হবে বা এটি একটি ত্রুটি ছুঁড়েছে।

আমার শ্রেণীর প্রতিটি সম্পত্তি বৈকল্পিক করা অপ্রয়োজনীয় ঝামেলার মতো মনে হচ্ছে যেহেতু আমি যা চাই তা হল জেএসনের মান বা একটি ডিফল্ট মান। (আমি সম্পত্তিটি শূন্য করতে চাই না))

এই কাজ করতে একটি উপায় আছে কি?

class MyCodable: Codable {
    var name: String = "Default Appleseed"
}

func load(input: String) {
    do {
        if let data = input.data(using: .utf8) {
            let result = try JSONDecoder().decode(MyCodable.self, from: data)
            print("name: \(result.name)")
        }
    } catch  {
        print("error: \(error)")
        // `Error message: "Key not found when expecting non-optional type
        // String for coding key \"name\""`
    }
}

let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional

আরও একটি প্রশ্ন আমার আমার জসনে একাধিক কী থাকলে আমি কী করতে পারি এবং জেসনকে মানচিত্রের জন্য জেনেরিক পদ্ধতিটি লিখতে চাইলে শুল্ক দেওয়ার পরিবর্তে এটি ন্যূনতম ডিফল্ট মান দেওয়া উচিত।
আদিত্য শর্মা

উত্তর:


22

আমি পছন্দ করি এমন পদ্ধতির মধ্যে তথাকথিত ডিটিও ব্যবহার করা হয় - ডেটা ট্রান্সফার অবজেক্ট। এটি একটি কাঠামো, যা কোডিংয়ের সাথে সামঞ্জস্য করে এবং পছন্দসই বস্তুর প্রতিনিধিত্ব করে।

struct MyClassDTO: Codable {
    let items: [String]?
    let otherVar: Int?
}

তারপরে আপনি কেবল সেই ডিটিও দিয়ে অ্যাপ্লিকেশনটিতে যে অবজেক্টটি ব্যবহার করতে চান তা খালি শুরু করেন।

 class MyClass {
    let items: [String]
    var otherVar = 3
    init(_ dto: MyClassDTO) {
        items = dto.items ?? [String]()
        otherVar = dto.otherVar ?? 3
    }

    var dto: MyClassDTO {
        return MyClassDTO(items: items, otherVar: otherVar)
    }
}

আপনি চাইলে চূড়ান্ত বস্তুর নাম পরিবর্তন এবং পরিবর্তন করতে পারেন তাই এই পদ্ধতিটিও ভাল। এটি পরিষ্কার এবং ম্যানুয়াল ডিকোডিংয়ের চেয়ে কম কোডের প্রয়োজন। তদুপরি, এই পদ্ধতির সাহায্যে আপনি অন্যান্য অ্যাপ্লিকেশন থেকে নেটওয়ার্কিং স্তর পৃথক করতে পারেন।


অন্যান্য কয়েকটি পদ্ধতির কিছু ভাল কাজ করেছে তবে শেষ পর্যন্ত আমি মনে করি এই লাইনগুলি বরাবরই কিছুটা সেরা পদ্ধতির।
জেকেল 4'19

জানা ভাল, কিন্তু কোড ডুপ্লিকেশন খুব বেশি আছে। আমি মার্টিন আর উত্তর পছন্দ করি
কামেন ডবরেভ

136

আপনি init(from decoder: Decoder)ডিফল্ট প্রয়োগ ব্যবহার না করে আপনার ধরণের পদ্ধতিটি প্রয়োগ করতে পারেন :

class MyCodable: Codable {
    var name: String = "Default Appleseed"

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let name = try container.decodeIfPresent(String.self, forKey: .name) {
            self.name = name
        }
    }
}

আপনি nameএকটি স্থির সম্পত্তিও (যদি আপনি চান) করতে পারেন:

class MyCodable: Codable {
    let name: String

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let name = try container.decodeIfPresent(String.self, forKey: .name) {
            self.name = name
        } else {
            self.name = "Default Appleseed"
        }
    }
}

অথবা

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
}

আপনার মন্তব্য পুনরায়: একটি কাস্টম এক্সটেনশন সহ

extension KeyedDecodingContainer {
    func decodeWrapper<T>(key: K, defaultValue: T) throws -> T
        where T : Decodable {
        return try decodeIfPresent(T.self, forKey: key) ?? defaultValue
    }
}

আপনি যেমন init পদ্ধতিটি প্রয়োগ করতে পারেন

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.name = try container.decodeWrapper(key: .name, defaultValue: "Default Appleseed")
}

তবে এটি এর চেয়ে কম নয়

    self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"

আরও মনে রাখবেন যে এই বিশেষ ক্ষেত্রে, আপনি স্বয়ংক্রিয়ভাবে উত্পন্ন CodingKeysগণনাটি ব্যবহার করতে পারেন (সুতরাং কাস্টম সংজ্ঞাটি মুছে ফেলতে পারেন) :)
হামিশ

@ হামিশ: আমি প্রথম চেষ্টা করার সময় এটি সংকলন করি নি, তবে এখন এটি কার্যকর হয় :)
মার্টিন আর

হ্যাঁ, এটি বর্তমানে কিছুটা প্যাচাল , তবে এটি ঠিক করা হবে ( বাগস.সুইফট.আর / ব্রাউজ / এসআর-5215 )
হামিশ

54
এটি এখনও হাস্যকর যে অটো-উত্পাদিত পদ্ধতিগুলি অ-বিকল্পগুলি থেকে ডিফল্ট মানগুলি পড়তে পারে না। আমার কাছে 8 টি বিকল্প এবং 1 টি অ-alচ্ছিক, সুতরাং এখন এনকোডার এবং ডিকোডার উভয় পদ্ধতিই ম্যানুয়ালি লিখলে অনেকগুলি বয়লারপ্লেট আসবে। ObjectMapperএটি খুব সুন্দরভাবে পরিচালনা করে
লেগোলেস

1
@ লিওডাবাস কি এমন হতে পারে যে আপনি মেনে চলছেন Decodableএবং আপনার নিজস্ব বাস্তবায়নও সরবরাহ করছেন init(from:)? সেক্ষেত্রে সংকলকটি ধরে নেয় আপনি নিজেই ডিকোডিং নিজেই পরিচালনা করতে চান এবং সুতরাং CodingKeysআপনার জন্য একটি এনাম সংশ্লেষিত করবেন না । আপনি যেমনটি বলেছেন, Codableপরিবর্তে তা অনুসারে কাজ করে কারণ এখন সংকলক আপনার জন্য সংশ্লেষ encode(to:)করছে এবং তেমনি সংশ্লেষও ঘটছে CodingKeys। আপনার কাছে আপনার নিজস্ব বাস্তবায়ন প্রদান তাহলে encode(to:), CodingKeysআর সংশ্লেষিত করা হবে না।
হামিশ

37

একটি সমাধান হ'ল জেএসওএন কীটি পাওয়া না গেলে এমন একটি গণিত সম্পত্তি ব্যবহার করা হবে যা পছন্দসই মানটিতে ডিফল্ট হয়। এটি আপনাকে আরও একটি সম্পত্তি ঘোষণার দরকার হওয়ায় এটি কিছু অতিরিক্ত শব্দবুদ্ধি যুক্ত করে এবং CodingKeysএনাম যুক্ত করতে হবে (যদি ইতিমধ্যে না থাকে)। সুবিধাটি হ'ল আপনার কাস্টম ডিকোডিং / এনকোডিং কোড লেখার দরকার নেই।

উদাহরণ স্বরূপ:

class MyCodable: Codable {
    var name: String { return _name ?? "Default Appleseed" }
    var age: Int?

    private var _name: String?

    enum CodingKeys: String, CodingKey {
        case _name = "name"
        case age
    }
}

আকর্ষণীয় পদ্ধতির। এটি কিছুটা কোড যুক্ত করে তবে এটি বস্তুটি তৈরি হওয়ার পরে এটি খুব স্পষ্ট এবং পরিদর্শনযোগ্য।
জেকেল

এই ইস্যুতে আমার প্রিয় উত্তর। এটি আমাকে এখনও ডিফল্ট JSONDecoder ব্যবহার করতে এবং সহজেই একটি ভেরিয়েবলের জন্য একটি ব্যতিক্রম করতে দেয়। ধন্যবাদ।
আইওএস_মাউস

দ্রষ্টব্য: এই পদ্ধতির ব্যবহার করে আপনার সম্পত্তিটি কেবলমাত্র একমাত্র হয়ে যায়, আপনি সরাসরি এই সম্পত্তিটির জন্য মূল্য নির্ধারণ করতে পারবেন না।
গণপত

8

আপনি বাস্তবায়ন করতে পারেন।

struct Source : Codable {

    let id : String?
    let name : String?

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case name = "name"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
        name = try values.decodeIfPresent(String.self, forKey: .name)
    }
}

হ্যাঁ এটিই সবচেয়ে পরিষ্কার উত্তর, তবে আপনার কাছে বড় আকারের জিনিস থাকলে এটি এখনও প্রচুর কোড পায়!
আশকান ঘোদ্রাত

1

আপনি যদি আপনার এনকোডিং এবং ডিকোডিং পদ্ধতিগুলি বাস্তবায়ন করতে না চান তবে ডিফল্ট মানগুলিতে কিছুটা নোংরা সমাধান রয়েছে।

আপনি আপনার নতুন ক্ষেত্রটিকে সুস্পষ্টভাবে মোড়কযুক্ত optionচ্ছিক হিসাবে ঘোষণা করতে পারেন এবং ডিকোডিংয়ের পরে এটি শুন্য কিনা এবং একটি ডিফল্ট মান সেট করতে পারেন।

আমি এটি কেবল প্রপার্টিলিস্ট এনকোডার দিয়ে পরীক্ষা করেছি, তবে আমি মনে করি জেএসএনইডিকোডার একইভাবে কাজ করে।


0

আপনি যদি মনে করেন যে আপনার নিজের সংস্করণটি লেখা init(from decoder: Decoder)অপ্রতিরোধ্য, আমি আপনাকে এমন একটি পদ্ধতি বাস্তবায়নের পরামর্শ দিচ্ছি যা ডিকোডারটিতে প্রেরণের আগে ইনপুটটি পরীক্ষা করে দেখবে। এইভাবে আপনার এমন একটি জায়গা থাকবে যেখানে আপনি ক্ষেত্রের অনুপস্থিতি যাচাই করতে পারেন এবং আপনার নিজের ডিফল্ট মান সেট করতে পারেন।

উদাহরণ স্বরূপ:

final class CodableModel: Codable
{
    static func customDecode(_ obj: [String: Any]) -> CodableModel?
    {
        var validatedDict = obj
        let someField = validatedDict[CodingKeys.someField.stringValue] ?? false
        validatedDict[CodingKeys.someField.stringValue] = someField

        guard
            let data = try? JSONSerialization.data(withJSONObject: validatedDict, options: .prettyPrinted),
            let model = try? CodableModel.decoder.decode(CodableModel.self, from: data) else {
                return nil
        }

        return model
    }

    //your coding keys, properties, etc.
}

এবং পরিবর্তে জেসন থেকে কোনও বস্তুর সূচনা করার জন্য:

do {
    let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
    let model = try CodableModel.decoder.decode(CodableModel.self, from: data)                        
} catch {
    assertionFailure(error.localizedDescription)
}

ইনিশ এর মতো দেখতে পাবেন:

if let vuvVideoFile = PublicVideoFile.customDecode($0) {
    videos.append(vuvVideoFile)
}

এই বিশেষ পরিস্থিতিতে আমি বিকল্পগুলির সাথে মোকাবিলা করতে পছন্দ করি তবে আপনার যদি অন্যরকম মতামত থাকে তবে আপনি নিজের কাস্টমডিকোড (:) পদ্ধতিটি ছুঁড়ে ফেলতে পারবেন


0

আমি ঠিক একই জিনিসটি খুঁজতে এই প্রশ্নটি জুড়ে এসেছি। আমি যে উত্তরগুলি পেয়েছি তা খুব সন্তোষজনক ছিল না যদিও আমি আশঙ্কা করেছিলাম যে সমাধানগুলির একমাত্র বিকল্প হতে পারে।

আমার ক্ষেত্রে, একটি কাস্টম ডিকোডার তৈরি করতে এক টন বয়লারপ্লেটের প্রয়োজন হবে যা বজায় রাখা শক্ত হবে তাই আমি অন্যান্য উত্তরগুলি সন্ধান করতে থাকি।

আমি এই নিবন্ধটিতে দৌড়েছি যা এটিকে ব্যবহার করে সাধারণ ক্ষেত্রে এটিকে কাটিয়ে উঠতে একটি আকর্ষণীয় উপায় দেখায় @propertyWrapper। আমার জন্য সবচেয়ে গুরুত্বপূর্ণ বিষয়টি ছিল এটি পুনরায় ব্যবহারযোগ্য এবং বিদ্যমান কোডের ন্যূনতম পুনঃনির্মাণের প্রয়োজন।

নিবন্ধটি এমন একটি ক্ষেত্রে ধরে নিয়েছে যেখানে আপনি কোনও অনুপস্থিত বুলিয়ান সম্পত্তি ডিফল্টকে মিথ্যা হিসাবে ব্যর্থ না করে অন্যরকম রূপগুলি প্রদর্শন করতে চান। আপনি এটিকে আরও বিশদে পড়তে পারেন তবে আমি আমার ব্যবহারের ক্ষেত্রে কী করেছি তা প্রদর্শন করব।

আমার ক্ষেত্রে, আমার কাছে একটি ছিল arrayযে আমি কীটি অনুপস্থিত থাকলে খালি হিসাবে আরম্ভ করতে চাই।

সুতরাং, আমি নিম্নলিখিত @propertyWrapperএবং অতিরিক্ত এক্সটেনশনগুলি ঘোষণা করেছি :

@propertyWrapper
struct DefaultEmptyArray<T:Codable> {
    var wrappedValue: [T] = []
}

//codable extension to encode/decode the wrapped value
extension DefaultEmptyArray: Codable {
    
    func encode(to encoder: Encoder) throws {
        try wrappedValue.encode(to: encoder)
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode([T].self)
    }
    
}

extension KeyedDecodingContainer {
    func decode<T:Decodable>(_ type: DefaultEmptyArray<T>.Type,
                forKey key: Key) throws -> DefaultEmptyArray<T> {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

এই পদ্ধতির সুবিধা হ'ল আপনি সহজেই @propertyWrapperসম্পত্তিটিতে যুক্ত করে বিদ্যমান কোডটিতে সহজেই সমস্যাটি কাটিয়ে উঠতে পারেন । আমার ক্ষেত্রে:

@DefaultEmptyArray var items: [String] = []

আশা করি এটি একই সমস্যা মোকাবেলায় কাউকে সহায়তা করবে।


হালনাগাদ:

এই বিষয়টি পোস্ট করার পরে এই বিষয়টি অব্যাহত রাখার পরে আমি এই অন্যান্য নিবন্ধটি পেয়েছি তবে সর্বাগ্রে @propertyWrapperএই বিষয়গুলির ক্ষেত্রে কিছু সাধারণ সহজে ব্যবহারযোগ্য এমন সংশ্লিষ্ট গ্রন্থাগারটি পাওয়া গেছে :

https://github.com/marksands/BetterCodable

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