সুইফট 4 ডিকোডেবল প্রোটোকলে প্রকারের JSON অভিধান সহ কোনও সম্পত্তি কীভাবে ডিকোড করবেন




swift swift4 (8)

ধরা যাক আমার কাছে Customer ডেটা টাইপ রয়েছে যার মধ্যে একটি metadata সম্পত্তি রয়েছে যা গ্রাহক অবজেক্টে কোনও জেএসএন অভিধান থাকতে পারে

struct Customer {
  let id: String
  let email: String
  let metadata: [String: Any]
}
{  
  "object": "customer",
  "id": "4yq6txdpfadhbaqnwp3",
  "email": "[email protected]",
  "metadata": {
    "link_id": "linked-id",
    "buy_count": 4
  }
}

metadata সম্পত্তি যেকোন স্বেচ্ছাসেবী JSON মানচিত্র অবজেক্ট হতে পারে।

আমি NSJSONDeserialization থেকে NSJSONDeserialization থেকে সম্পত্তি কাস্ট করতে পারার আগে কিন্তু নতুন সুইফট 4 Decodable প্রোটোকল দিয়ে, আমি এখনও এটি করার উপায় সম্পর্কে ভাবতে পারি না।

ডিকোডেবল প্রোটোকলের সাহায্যে কেউ কীভাবে সুইফট 4 এ এটি অর্জন করবেন জানেন?


আপনার BeyovaJSON তে একটি নজর থাকতে পারে

import BeyovaJSON

struct Customer: Codable {
  let id: String
  let email: String
  let metadata: JToken
}

//create a customer instance

customer.metadata = ["link_id": "linked-id","buy_count": 4]

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted 
print(String(bytes: try! encoder.encode(customer), encoding: .utf8)!)

আপনি JSON পার্স করার জন্য যদি SwiftyJSON ব্যবহার করেন তবে আপনি 4.1.0 আপডেট করতে পারবেন যার Codable প্রোটোকল সমর্থন রয়েছে। কেবল মেটাটাটা ঘোষণা করুন metadata: JSON এবং আপনি প্রস্তুত all

import SwiftyJSON

struct Customer {
  let id: String
  let email: String
  let metadata: JSON
}

আপনি যা চান Codable ডিজাইনের বিপরীতে যান। Codable পেছনের Codable হ'ল টাইপ-নিরাপদ উপায়ে সংরক্ষণাগারভুক্ত ও সংরক্ষণাগারহীন ডেটা করার একটি ব্যবস্থা সরবরাহ করা। এর অর্থ হ'ল হাত দেওয়ার আগে আপনাকে অবশ্যই বৈশিষ্ট্য এবং তাদের ডেটা ধরণের সংজ্ঞা দিতে হবে। আমি আপনার সমস্যার 2 টি সমাধান সম্পর্কে ভাবতে পারি:

1. সমস্ত সম্ভাব্য মেটাডেটা কী তালিকাভুক্ত করুন

প্রায়শই, আপনি যদি API এর ডকুমেন্টেশনের গভীরে পৌঁছে যান তবে আপনি সমস্ত সম্ভাব্য মেটাডেটা কীগুলির সম্পূর্ণ তালিকা পাবেন। এই কীগুলি Metadata চ্ছিক বৈশিষ্ট্য হিসাবে একটি Metadata স্ট্রাক্ট সংজ্ঞায়িত করুন:

struct Customer {
    private struct RawCustomer: Decodable {
        var object: String
        var id: String
        var email: String
    }

    var object: String
    var id: String
    var email: String
    var metadata: [String: AnyObject]

    init(jsonData: Data) throws {
        let rawCustomer = try JSONDecoder().decode(RawCustomer.self, from: jsonData)
        object = rawCustomer.object
        id     = rawCustomer.id
        email  = rawCustomer.email

        let jsonObject = try JSONSerialization.jsonObject(with: jsonData)
        if let dict = jsonObject as? [String: AnyObject],
            let metadata = dict["metadata"] as? [String: AnyObject]
        {
            self.metadata = metadata
        } else {
            self.metadata = [String: AnyObject]()
        }
    }
}

let customer = try! Customer(jsonData: jsonData)
print(customer.metadata)

আমি দেখতে পাচ্ছি যে সুইফ্ট ডিজাইনাররা এই পদ্ধতির পছন্দ করতেন।

২. ডিকোডেবল এবং জেএসএনএসরাইজেশন একত্রিত করুন

প্রকার সুরক্ষার জন্য JSONSerialization বাণিজ্যে দুর্দান্ত গতিশীলতা সরবরাহ করে। আপনি এটি অবশ্যই Decodable সাথে মিশ্রিত করতে পারেন, যার নকশার দর্শন একেবারে বিপরীত:

 struct Customer { private struct RawCustomer: Decodable { var object: String var id: String var email: String } var object: String var id: String var email: String var metadata: [String: AnyObject] init(jsonData: Data) throws { let rawCustomer = try JSONDecoder().decode(RawCustomer.self, from: jsonData) object = rawCustomer.object id = rawCustomer.id email = rawCustomer.email let jsonObject = try JSONSerialization.jsonObject(with: jsonData) if let dict = jsonObject as? [String: AnyObject], let metadata = dict["metadata"] as? [String: AnyObject] { self.metadata = metadata } else { self.metadata = [String: AnyObject]() } } } let customer = try! Customer(jsonData: jsonData) print(customer.metadata) 

আমি এই UnkeyedDecodingContainer থেকে কিছু অনুপ্রেরণা পেয়েছি, আমি UnkeyedDecodingContainer এবং KeyedDecodingContainer UnkeyedDecodingContainer জন্য কিছু এক্সটেনশন KeyedDecodingContainer । আপনি আমার টুকরোটির লিঙ্কটি here পেতে পারেন। এই কোডটি ব্যবহার করে আপনি এখন পরিচিত সিনট্যাক্স সহ যে কোনও Array<Any> বা Dictionary<String, Any> ডিকোড করতে পারেন:

let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)

অথবা

let array: [Any] = try container.decode([Any].self, forKey: key)

সম্পাদনা করুন: আমি খুঁজে পেয়েছি এমন একটি ক্যাভেট রয়েছে যা অভিধানের একটি অ্যারে ডিকোড করছে [[String: Any]] প্রয়োজনীয় বাক্য গঠনটি নিম্নরূপ। আপনি সম্ভবত বল প্রয়োগের পরিবর্তে একটি ত্রুটি নিক্ষেপ করতে চাইবেন:

let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]

সম্পাদনা 2: আপনি যদি কেবল একটি সম্পূর্ণ ফাইলকে একটি অভিধানে রূপান্তর করতে চান তবে আপনি JSONSerialization থেকে এপিআই দিয়ে আটকে থাকা ভাল কারণ আমি কোনও অভিধান ডিকোড করতে সরাসরি JSONDecoder প্রসারিত করার উপায় খুঁজে পাইনি।

guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
  // appropriate error handling
  return
}

এক্সটেনশনগুলি

// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a

struct JSONCodingKeys: CodingKey {
    var stringValue: String

    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    var intValue: Int?

    init?(intValue: Int) {
        self.init(stringValue: "\(intValue)")
        self.intValue = intValue
    }
}


extension KeyedDecodingContainer {

    func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> {
        let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
        guard contains(key) else { 
            return nil
        }
        guard try decodeNil(forKey: key) == false else { 
            return nil 
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> {
        var container = try self.nestedUnkeyedContainer(forKey: key)
        return try container.decode(type)
    }

    func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? {
        guard contains(key) else {
            return nil
        }
        guard try decodeNil(forKey: key) == false else { 
            return nil 
        }
        return try decode(type, forKey: key)
    }

    func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
        var dictionary = Dictionary<String, Any>()

        for key in allKeys {
            if let boolValue = try? decode(Bool.self, forKey: key) {
                dictionary[key.stringValue] = boolValue
            } else if let stringValue = try? decode(String.self, forKey: key) {
                dictionary[key.stringValue] = stringValue
            } else if let intValue = try? decode(Int.self, forKey: key) {
                dictionary[key.stringValue] = intValue
            } else if let doubleValue = try? decode(Double.self, forKey: key) {
                dictionary[key.stringValue] = doubleValue
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedDictionary
            } else if let nestedArray = try? decode(Array<Any>.self, forKey: key) {
                dictionary[key.stringValue] = nestedArray
            }
        }
        return dictionary
    }
}

extension UnkeyedDecodingContainer {

    mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
        var array: [Any] = []
        while isAtEnd == false {
            // See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays.
            if try decodeNil() {
                continue
            } else if let value = try? decode(Bool.self) {
                array.append(value)
            } else if let value = try? decode(Double.self) {
                array.append(value)
            } else if let value = try? decode(String.self) {
                array.append(value)
            } else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) {
                array.append(nestedDictionary)
            } else if let nestedArray = try? decode(Array<Any>.self) {
                array.append(nestedArray)
            }
        }
        return array
    }

    mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {

        let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self)
        return try nestedContainer.decode(type)
    }
}

আমি যখন পুরানো উত্তরটি পেয়েছি তখন আমি কেবল একটি সাধারণ জেএসওএন অবজেক্ট কেসটি পরীক্ষা করেছি তবে একটি খালি নয় যা @ স্লুরমোমেটিক এবং @ জুলের মতো রানটাইম ব্যতিক্রম ঘটায়। এই সমস্যাটির জন্য দুঃখিত।

সুতরাং আমি একটি সাধারণ JSONValue প্রোটোকল রেখে আর কোনও উপায়ে চেষ্টা করি, AnyJSONValue প্রকারের AnyJSONValue প্রয়োগ করি এবং যে AnyJSONValue পরিবর্তে সেই ধরণের ব্যবহার করি। এখানে একটি বাস্তবায়ন।

public protocol JSONType: Decodable {
    var jsonValue: Any { get }
}

extension Int: JSONType {
    public var jsonValue: Any { return self }
}
extension String: JSONType {
    public var jsonValue: Any { return self }
}
extension Double: JSONType {
    public var jsonValue: Any { return self }
}
extension Bool: JSONType {
    public var jsonValue: Any { return self }
}

public struct AnyJSONType: JSONType {
    public let jsonValue: Any

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()

        if let intValue = try? container.decode(Int.self) {
            jsonValue = intValue
        } else if let stringValue = try? container.decode(String.self) {
            jsonValue = stringValue
        } else if let boolValue = try? container.decode(Bool.self) {
            jsonValue = boolValue
        } else if let doubleValue = try? container.decode(Double.self) {
            jsonValue = doubleValue
        } else if let doubleValue = try? container.decode(Array<AnyJSONType>.self) {
            jsonValue = doubleValue
        } else if let doubleValue = try? container.decode(Dictionary<String, AnyJSONType>.self) {
            jsonValue = doubleValue
        } else {
            throw DecodingError.typeMismatch(JSONType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON tyep"))
        }
    }
}

এবং ডিকোডিংয়ের সময় এটি কীভাবে ব্যবহার করবেন তা এখানে

metadata = try container.decode ([String: AnyJSONValue].self, forKey: .metadata)

এই সমস্যাটি হ'ল সমস্যাটি হ'ল আমাদের অবশ্যই কল করতে হবে। value.jsonValue as? Int value.jsonValue as? Int । আমাদের Conditional Conformance অবধি অবধি অপেক্ষা করা দরকার, যা এই সমস্যার সমাধান করবে বা কমপক্ষে এটি আরও ভাল হতে সহায়তা করবে।

[পুরানো উত্তর]

আমি এই প্রশ্নটি অ্যাপল বিকাশকারী ফোরামে পোস্ট করেছি এবং এটি খুব সহজেই দেখা যাচ্ছে।

আমি করতে পারি

metadata = try container.decode ([String: Any].self, forKey: .metadata)

ইনিশিয়ালাইজারে।

প্রথম স্থানটি মিস করা আমার খারাপ ছিল।


আমিও এই সমস্যার সাথে খেলেছি এবং অবশেষে "জেনেরিক জেএসওএন" প্রকারের সাথে কাজ করার জন্য একটি সাধারণ লাইব্রেরি লিখেছি। (যেখানে "জেনেরিক" এর অর্থ "কোনও কাঠামো আগেই জানা নেই")) মূল বিষয়টি একটি কংক্রিটের ধরণের জেনেরিক জেএসএনকে উপস্থাপন করছে:

public enum JSON {
    case string(String)
    case number(Float)
    case object([String:JSON])
    case array([JSON])
    case bool(Bool)
    case null
}

এই ধরণের পরে Codable এবং Equatable প্রয়োগ করতে পারে।


এখানে আরও জেনেরিক (কেবল [String: Any] , তবে [Any] ডিকোড করা যায়) এবং এনক্যাপসুলেটেড অ্যাপ্রোচ (তার জন্য পৃথক সত্তা ব্যবহৃত হয়) @ লাউডমাউথ উত্তরের দ্বারা অনুপ্রাণিত।

এটি ব্যবহার করার মতো দেখতে পাবেন:

extension Customer: Decodable {
  public init(from decoder: Decoder) throws {
    let selfContainer = try decoder.container(keyedBy: CodingKeys.self)
    id = try selfContainer.decode(.id)
    email = try selfContainer.decode(.email)
    let metadataContainer: JsonContainer = try selfContainer.decode(.metadata)
    guard let metadata = metadataContainer.value as? [String: Any] else {
      let context = DecodingError.Context(codingPath: [CodingKeys.metadata], debugDescription: "Expected '[String: Any]' for 'metadata' key")
      throw DecodingError.typeMismatch([String: Any].self, context)
    }
    self.metadata = metadata
  }

  private enum CodingKeys: String, CodingKey {
    case id, email, metadata
  }
}

JsonContainer সত্তা যা আমরা JSON অবজেক্টে JSON ডেটা ডিকোডিংয়ের জন্য ব্যবহার করি (অ্যারে বা অভিধান হয়) *DecodingContainer (তাই এটি বিরল ক্ষেত্রে হস্তক্ষেপ করবে না যখন কোনও JSON অবজেক্ট [String: Any] দ্বারা বোঝানো হয়নি)।

struct JsonContainer {

  let value: Any
}

extension JsonContainer: Decodable {

  public init(from decoder: Decoder) throws {
    if let keyedContainer = try? decoder.container(keyedBy: Key.self) {
      var dictionary = [String: Any]()
      for key in keyedContainer.allKeys {
        if let value = try? keyedContainer.decode(Bool.self, forKey: key) {
          // Wrapping numeric and boolean types in `NSNumber` is important, so `as? Int64` or `as? Float` casts will work
          dictionary[key.stringValue] = NSNumber(value: value)
        } else if let value = try? keyedContainer.decode(Int64.self, forKey: key) {
          dictionary[key.stringValue] = NSNumber(value: value)
        } else if let value = try? keyedContainer.decode(Double.self, forKey: key) {
          dictionary[key.stringValue] = NSNumber(value: value)
        } else if let value = try? keyedContainer.decode(String.self, forKey: key) {
          dictionary[key.stringValue] = value
        } else if (try? keyedContainer.decodeNil(forKey: key)) ?? false {
          // NOP
        } else if let value = try? keyedContainer.decode(JsonContainer.self, forKey: key) {
          dictionary[key.stringValue] = value.value
        } else {
          throw DecodingError.dataCorruptedError(forKey: key, in: keyedContainer, debugDescription: "Unexpected value for \(key.stringValue) key")
        }
      }
      value = dictionary
    } else if var unkeyedContainer = try? decoder.unkeyedContainer() {
      var array = [Any]()
      while !unkeyedContainer.isAtEnd {
        let container = try unkeyedContainer.decode(JsonContainer.self)
        array.append(container.value)
      }
      value = array
    } else if let singleValueContainer = try? decoder.singleValueContainer() {
      if let value = try? singleValueContainer.decode(Bool.self) {
        self.value = NSNumber(value: value)
      } else if let value = try? singleValueContainer.decode(Int64.self) {
        self.value = NSNumber(value: value)
      } else if let value = try? singleValueContainer.decode(Double.self) {
        self.value = NSNumber(value: value)
      } else if let value = try? singleValueContainer.decode(String.self) {
        self.value = value
      } else if singleValueContainer.decodeNil() {
        value = NSNull()
      } else {
        throw DecodingError.dataCorruptedError(in: singleValueContainer, debugDescription: "Unexpected value")
      }
    } else {
      let context = DecodingError.Context(codingPath: [], debugDescription: "Invalid data format for JSON")
      throw DecodingError.dataCorrupted(context)
    }
  }

  private struct Key: CodingKey {
    var stringValue: String

    init?(stringValue: String) {
      self.stringValue = stringValue
    }

    var intValue: Int?

    init?(intValue: Int) {
      self.init(stringValue: "\(intValue)")
      self.intValue = intValue
    }
  }
}

মনে রাখবেন যে সংখ্যাযুক্ত এবং বুলিয়ান NSNumber দ্বারা NSNumber , অন্য কিছু এর মতো কাজ করবে না:

if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil

ডিকোডিং + এনকোডিং [String: Any] , [Any] সহজ করার জন্য আমি একটি পড তৈরি করেছি। এবং এটি এনকোড প্রদান করে বা optionচ্ছিক বৈশিষ্ট্যগুলি ডিকোড করে এখানে https://github.com/levantAJ/AnyCodable

pod 'DynamicCodable', '1.0'

এটি কিভাবে ব্যবহার করতে:

import DynamicCodable

struct YourObject: Codable {
    var dict: [String: Any]
    var array: [Any]
    var optionalDict: [String: Any]?
    var optionalArray: [Any]?

    enum CodingKeys: String, CodingKey {
        case dict
        case array
        case optionalDict
        case optionalArray
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        dict = try values.decode([String: Any].self, forKey: .dict)
        array = try values.decode([Any].self, forKey: .array)
        optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
        optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(dict, forKey: .dict)
        try container.encode(array, forKey: .array)
        try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
        try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
    }
}






codable