วิธีถอดรหัสคุณสมบัติด้วยประเภทของพจนานุกรม JSON ในโปรโตคอลที่ถอดรหัสได้ Swift [45]


114

สมมติว่าฉันมีCustomerประเภทข้อมูลที่มีmetadataคุณสมบัติที่สามารถมีพจนานุกรม JSON ในออบเจ็กต์ของลูกค้าได้

struct Customer {
  let id: String
  let email: String
  let metadata: [String: Any]
}

{  
  "object": "customer",
  "id": "4yq6txdpfadhbaqnwp3",
  "email": "john.doe@example.com",
  "metadata": {
    "link_id": "linked-id",
    "buy_count": 4
  }
}

metadataคุณสมบัติสามารถใด ๆ โดยพล JSON แผนที่วัตถุ

ก่อนที่ฉันจะสามารถแคสต์คุณสมบัติจาก JSON ที่ไม่กำหนดค่าซีเรียลจากNSJSONDeserializationแต่ด้วยDecodableโปรโตคอลSwift 4 ใหม่ฉันยังไม่สามารถคิดวิธีที่จะทำเช่นนั้นได้

มีใครรู้วิธีที่จะบรรลุสิ่งนี้ใน Swift 4 พร้อมโปรโตคอล Decodable หรือไม่?

คำตอบ:


96

ด้วยแรงบันดาลใจจากกระทู้นี้ผมพบว่าผมเขียนส่วนขยายบางอย่างสำหรับการและUnkeyedDecodingContainer KeyedDecodingContainerคุณสามารถค้นหาเชื่อมโยงไปยังส่วนสำคัญของฉันที่นี่ โดยใช้รหัสนี้ตอนนี้คุณสามารถถอดรหัสใด ๆ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:หากคุณเพียงแค่ต้องการแปลงไฟล์ทั้งหมดเป็นพจนานุกรมคุณควรใช้ api จาก 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)
    }
}

น่าสนใจเดี๋ยวจะลองทำดูและจะอัพเดทผลงานให้ฟังนะครับ @loudmouth
Pitiphong Phongpattranont

@PitiphongPhongpattranont รหัสนี้ได้ผลสำหรับคุณหรือไม่?
loudmouth

1
@JonBrooks สภาพสุดท้ายในในUnkeyedDecodingContainer's decode(_ type: Array<Any>.Type) throws -> Array<Any>คือการตรวจสอบหาที่ซ้อนกันอาร์เรย์ ดังนั้นหากคุณมีโครงสร้างข้อมูลที่มีลักษณะดังต่อไปนี้[true, 452.0, ["a", "b", "c"] ] มันจะดึง["a", "b", "c"]อาร์เรย์ที่ซ้อนกัน decodeวิธีการของUnkeyedDecodingContainer"พ่อ" จากองค์ประกอบจากภาชนะ ไม่ควรทำให้เกิดการเรียกซ้ำไม่สิ้นสุด
loudmouth

1
@loudmouth มันเป็นไปได้ที่จะมีค่าเป็นศูนย์สำหรับคีย์ใน {"array": null}JSON: ดังนั้นคุณguard contains(key)จะผ่านไป แต่มันจะผิดพลาดไม่กี่บรรทัดในภายหลังเมื่อพยายามถอดรหัสค่า null สำหรับคีย์ "array" decodeดังนั้นมันจะดีกว่าที่จะเพิ่มเงื่อนไขหนึ่งมากขึ้นในการตรวจสอบว่ามีค่าเป็นจริงไม่เป็นโมฆะก่อนที่จะเรียก
chebur

2
ฉันพบวิธีแก้ไข: แทนที่จะ} else if let nestedArray = try? decode(Array<Any>.self, forKey: key)ลอง:} else if var nestedContainer = try? nestedUnkeyedContainer(), let nestedArray = try? nestedContainer.decode(Array<Any>.self) {
Jon Brooks

23

ผมได้เล่นกับปัญหานี้มากเกินไปและในที่สุดก็เขียนห้องสมุดง่ายสำหรับการทำงานกับ“JSON ทั่วไป” ประเภท (โดยที่ "ทั่วไป" หมายถึง "โดยไม่ทราบโครงสร้างล่วงหน้า") ประเด็นหลักคือการแสดง JSON ทั่วไปด้วยประเภทคอนกรีต:

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

ประเภทนี้ก็สามารถดำเนินการและCodableEquatable


14

คุณสามารถสร้างโครงสร้างข้อมูลเมตาซึ่งยืนยันกับDecodableโปรโตคอลและใช้JSONDecoderคลาสเพื่อสร้างวัตถุจากข้อมูลโดยใช้วิธีการถอดรหัสดังต่อไปนี้

let json: [String: Any] = [
    "object": "customer",
    "id": "4yq6txdpfadhbaqnwp3",
    "email": "john.doe@example.com",
    "metadata": [
        "link_id": "linked-id",
        "buy_count": 4
    ]
]

struct Customer: Decodable {
    let object: String
    let id: String
    let email: String
    let metadata: Metadata
}

struct Metadata: Decodable {
    let link_id: String
    let buy_count: Int
}

let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)

let decoder = JSONDecoder()
do {
    let customer = try decoder.decode(Customer.self, from: data)
    print(customer)
} catch {
    print(error.localizedDescription)
}

12
ไม่ฉันทำไม่ได้เพราะฉันไม่รู้โครงสร้างของmetadataค่า อาจเป็นวัตถุใด ๆ ก็ได้
Pitiphong Phongpattranont

คุณหมายถึงเป็นประเภท Array หรือ Dictionary ก็ได้?
Suhit Patil

คุณสามารถยกตัวอย่างหรืออธิบายเพิ่มเติมเกี่ยวกับโครงสร้างข้อมูลเมตาได้หรือไม่
Suhit Patil

2
ค่าของmetadataสามารถเป็นออบเจ็กต์ JSON ใดก็ได้ ดังนั้นอาจเป็นพจนานุกรมว่างเปล่าหรือพจนานุกรมใดก็ได้ "metadata": {} "metadata": {user_id: "id"} "metadata": {ค่ากำหนด: {shows_value: true, language: "en"}} เป็นต้น
Pitiphong Phongpattranont

ทางเลือกหนึ่งที่เป็นไปได้คือการใช้พารามิเตอร์ทั้งหมดในโครงสร้างข้อมูลเมตาเป็นตัวเลือกและแสดงรายการค่าที่เป็นไปได้ทั้งหมดในโครงสร้างข้อมูลเมตาเช่นโครงสร้างข้อมูลเมตา {var user_id: String? ค่ากำหนด var: String? }
Suhit Patil

8

ฉันมาพร้อมกับวิธีแก้ปัญหาที่แตกต่างกันเล็กน้อย

สมมติว่าเรามีอะไรที่มากกว่าธรรมดา [String: Any]แยกวิเคราะห์คือ Any อาจเป็นอาร์เรย์หรือพจนานุกรมที่ซ้อนกันหรือพจนานุกรมอาร์เรย์

สิ่งนี้:

var json = """
{
  "id": 12345,
  "name": "Giuseppe",
  "last_name": "Lanza",
  "age": 31,
  "happy": true,
  "rate": 1.5,
  "classes": ["maths", "phisics"],
  "dogs": [
    {
      "name": "Gala",
      "age": 1
    }, {
      "name": "Aria",
      "age": 3
    }
  ]
}
"""

นี่คือทางออกของฉัน:

public struct AnyDecodable: Decodable {
  public var value: Any

  private struct CodingKeys: CodingKey {
    var stringValue: String
    var intValue: Int?
    init?(intValue: Int) {
      self.stringValue = "\(intValue)"
      self.intValue = intValue
    }
    init?(stringValue: String) { self.stringValue = stringValue }
  }

  public init(from decoder: Decoder) throws {
    if let container = try? decoder.container(keyedBy: CodingKeys.self) {
      var result = [String: Any]()
      try container.allKeys.forEach { (key) throws in
        result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value
      }
      value = result
    } else if var container = try? decoder.unkeyedContainer() {
      var result = [Any]()
      while !container.isAtEnd {
        result.append(try container.decode(AnyDecodable.self).value)
      }
      value = result
    } else if let container = try? decoder.singleValueContainer() {
      if let intVal = try? container.decode(Int.self) {
        value = intVal
      } else if let doubleVal = try? container.decode(Double.self) {
        value = doubleVal
      } else if let boolVal = try? container.decode(Bool.self) {
        value = boolVal
      } else if let stringVal = try? container.decode(String.self) {
        value = stringVal
      } else {
        throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
      }
    } else {
      throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
    }
  }
}

ลองใช้

let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)

7

เมื่อฉันพบคำตอบเก่าฉันทดสอบเฉพาะกรณีออบเจ็กต์ JSON ธรรมดา แต่ไม่ใช่กรณีว่างเปล่าซึ่งจะทำให้เกิดข้อยกเว้นรันไทม์เช่น @slurmomatic และ @zoul พบ ขออภัยสำหรับปัญหานี้

ดังนั้นฉันจึงลองวิธีอื่นด้วยการมีโปรโตคอล JSONValue แบบง่ายใช้AnyJSONValuetype erasure struct และใช้ประเภทนั้นแทนAny. นี่คือการใช้งาน

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. เราต้องรอจนกว่าจะConditional Conformanceลงจอดใน Swift ซึ่งจะช่วยแก้ปัญหานี้หรืออย่างน้อยก็ช่วยให้ดีขึ้น


[คำตอบเก่า]

ฉันโพสต์คำถามนี้ในฟอรัมนักพัฒนาของ Apple และปรากฎว่ามันง่ายมาก

ที่ฉันสามารถทำได้

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

ในโปรแกรมเริ่มต้น

มันเป็นเรื่องแย่ของฉันที่พลาดสิ่งนั้นในตอนแรก


4
สามารถโพสต์ลิงก์ไปยังคำถามใน Apple Developer Anyไม่เป็นไปตามDecodableดังนั้นฉันจึงไม่แน่ใจว่านี่คือคำตอบที่ถูกต้องอย่างไร
Reza Shirazian

@RezaShirazian นั่นคือสิ่งที่ฉันคิดไว้ตั้งแต่แรก แต่ปรากฎว่า Dictionary สอดคล้องกับ Encodable เมื่อคีย์สอดคล้องกับ Hashable และไม่ขึ้นอยู่กับค่าของมัน คุณสามารถเปิดส่วนหัวของพจนานุกรมและดูได้ด้วยตัวเอง พจนานุกรมนามสกุล: เข้ารหัสโดยที่ Key: Hashable extension Dictionary: Decodable where Key: Hashable forums.developer.apple.com/thread/80288#237680
Pitiphong Phongpattranont

6
ขณะนี้ใช้งานไม่ได้ "พจนานุกรม <String, Any> ไม่สอดคล้องกับ Decodable เนื่องจาก Any ไม่สอดคล้องกับ Decodable"
mbuchetics

ปรากฎว่ามันใช้งานได้ ฉันใช้มันในรหัสของฉัน คุณต้องเข้าใจว่าไม่มีวิธีใดที่จะแสดงข้อกำหนดที่ว่า "Value of Dictionary ต้องสอดคล้องกับโปรโตคอล Decodable เพื่อที่จะทำให้ Dictionary สอดคล้องกับ Decodable protocol" ในตอนนี้ นั่นคือ "Conditional Conformance" ซึ่งยังไม่ได้ใช้ใน Swift 4 ฉันคิดว่าตอนนี้ก็โอเคแล้วเนื่องจากมีข้อ จำกัด มากมายใน Swift Type System (และ Generics) ดังนั้นสิ่งนี้ใช้ได้ผลในตอนนี้ แต่เมื่อ Swift Type System ได้รับการปรับปรุงในอนาคต (โดยเฉพาะอย่างยิ่งเมื่อมีการนำ Conditional Conformance มาใช้) สิ่งนี้ไม่ควรใช้งานได้
Pitiphong Phongpattranont

3
ใช้งานไม่ได้สำหรับฉันตั้งแต่ Xcode 9 beta 5 คอมไพล์ แต่ระเบิดเมื่อรันไทม์: Dictionary <String, Any> ไม่สอดคล้องกับ Decodable เนื่องจาก Any ไม่สอดคล้องกับ Decodable
zoul

6

หากคุณใช้SwiftyJSONเพื่อแยกวิเคราะห์ JSON คุณสามารถอัปเดตเป็น4.1.0ซึ่งCodableรองรับโปรโตคอล เพียงแค่ประกาศmetadata: JSONและคุณก็พร้อมแล้ว

import SwiftyJSON

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

ฉันไม่รู้ว่าทำไมคำตอบนี้จึงถูกลดลง มันถูกต้องโดยสิ้นเชิงและแก้ไขปัญหาได้
Leonid Usov

ดูเหมือนว่าจะดีสำหรับการย้ายจาก SwiftyJSON ไปยัง Decodable
Michał Ziobro

วิธีนี้ไม่ได้ช่วยแก้ปัญหาในการแยกวิเคราะห์ metadata json ซึ่งเป็นปัญหาเดิม
llamacorn

1

คุณอาจได้ดู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 ทั่วไปเป็น JToken ต่อท้ายค่าบางอย่างและส่งกลับไปยังเซิร์ฟเวอร์ ดีมากแน่นอน นั่นคืองานที่ยอดเยี่ยมที่คุณทำ :)
Vitor Hugo Schwaab

1

ผมได้ทำฝักเพื่ออำนวยความสะดวกวิธีการถอดรหัสการเข้ารหัส + ,[String: Any] [Any]และนี่เป็นการเข้ารหัสหรือถอดรหัสคุณสมบัติทางเลือกที่นี่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)
    }
}

0

วิธีที่ง่ายที่สุดและแนะนำคือการสร้างรูปแบบแยกต่างหากสำหรับแต่ละพจนานุกรมหรือรุ่นที่อยู่ใน JSON

นี่คือสิ่งที่ฉันทำ

//Model for dictionary **Metadata**

struct Metadata: Codable {
    var link_id: String?
    var buy_count: Int?
}  

//Model for dictionary **Customer**

struct Customer: Codable {
   var object: String?
   var id: String?
   var email: String?
   var metadata: Metadata?
}

//Here is our decodable parser that decodes JSON into expected model

struct CustomerParser {
    var customer: Customer?
}

extension CustomerParser: Decodable {

//keys that matches exactly with JSON
enum CustomerKeys: String, CodingKey {
    case object = "object"
    case id = "id"
    case email = "email"
    case metadata = "metadata"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CustomerKeys.self) // defining our (keyed) container

    let object: String = try container.decode(String.self, forKey: .object) // extracting the data
    let id: String = try container.decode(String.self, forKey: .id) // extracting the data
    let email: String = try container.decode(String.self, forKey: .email) // extracting the data

   //Here I have used metadata model instead of dictionary [String: Any]
    let metadata: Metadata = try container.decode(Metadata.self, forKey: .metadata) // extracting the data

    self.init(customer: Customer(object: object, id: id, email: email, metadata: metadata))

    }
}

การใช้งาน:

  if let url = Bundle.main.url(forResource: "customer-json-file", withExtension: "json") {
        do {
            let jsonData: Data =  try Data(contentsOf: url)
            let parser: CustomerParser = try JSONDecoder().decode(CustomerParser.self, from: jsonData)
            print(parser.customer ?? "null")

        } catch {

        }
    }

** ฉันได้ใช้ทางเลือกเพื่อให้อยู่ในด้านที่ปลอดภัยขณะแยกวิเคราะห์สามารถเปลี่ยนแปลงได้ตามต้องการ

อ่านเพิ่มเติมในหัวข้อนี้


1
คำตอบของคุณคือคำตอบที่เหมาะสมสำหรับ Swift 4.1 อย่างแน่นอนและบรรทัดแรกของโพสต์ของคุณก็หมดลง! สมมติว่าข้อมูลมาจากบริการเว็บ คุณสามารถสร้างแบบจำลองวัตถุที่ซ้อนกันอย่างง่ายจากนั้นใช้ไวยากรณ์จุดเพื่อคว้าแต่ละรายการ ดูคำตอบของ suhit ด้านล่าง
David H

0

นี่เป็นวิธีการทั่วไปมากขึ้น (ไม่เพียง[String: Any]แต่[Any]สามารถถอดรหัสได้) และวิธีการห่อหุ้ม (ใช้เอนทิตีแยกต่างหาก) ซึ่งได้รับแรงบันดาลใจจากคำตอบของ @loudmouth

การใช้งานจะมีลักษณะดังนี้:

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มิฉะนั้นจะใช้ไม่ได้

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

ฉันสามารถถอดรหัสเฉพาะคุณสมบัติที่เลือกและปล่อยให้มีการถอดรหัสอื่น ๆ โดยอัตโนมัติเนื่องจากฉันมีคุณสมบัติ 15 อย่างที่เพียงพอการถอดรหัสอัตโนมัติและอาจเป็น 3 รายการที่ต้องการการจัดการการถอดรหัสแบบกำหนดเอง
Michał Ziobro

@ MichałZiobroคุณต้องการบางส่วนของข้อมูลที่ถอดรหัสเป็นออบเจ็กต์ JSON และส่วนหนึ่งของข้อมูลนั้นถอดรหัสเป็นตัวแปรอินสแตนซ์แยกกันหรือไม่? หรือคุณกำลังถามเกี่ยวกับการเขียนโปรแกรมเริ่มต้นการถอดรหัสบางส่วนสำหรับส่วนหนึ่งของวัตถุ (และไม่มีอะไรที่เหมือนกันกับโครงสร้างแบบ JSON)? สำหรับความรู้ของฉันคำตอบสำหรับคำถามแรกคือใช่คำถามที่สองคือไม่ใช่
Alexey Kozhevnikov

ฉันต้องการมีคุณสมบัติบางอย่างที่มีการถอดรหัสแบบกำหนดเองและส่วนที่เหลือพร้อมการถอดรหัสเริ่มต้นมาตรฐาน
Michał Ziobro

@ MichałZiobroถ้าฉันเข้าใจคุณถูกต้องมันเป็นไปไม่ได้ อย่างไรก็ตามคำถามของคุณไม่เกี่ยวข้องกับคำถาม SO ปัจจุบันและคุ้มค่ากับคำถามแยกต่างหาก
Alexey Kozhevnikov

0

ถอดรหัสโดยใช้ตัวถอดรหัสและคีย์การเข้ารหัส

public let dataToDecode: [String: AnyDecodable]

enum CodingKeys: CodingKey {
    case dataToDecode
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.dataToDecode = try container.decode(Dictionary<String, AnyDecodable>.self, forKey: .dataToDecode) 
}    

0

รายละเอียด

  • Xcode 12.0.1 (12A7300)
  • สวิฟต์ 5.3

ขึ้นอยู่กับห้องสมุดTai Le

// code from: https://github.com/levantAJ/AnyCodable/blob/master/AnyCodable/DecodingContainer%2BAnyCollection.swift

private
struct AnyCodingKey: CodingKey {
    let stringValue: String
    private (set) var intValue: Int?
    init?(stringValue: String) { self.stringValue = stringValue }
    init?(intValue: Int) {
        self.intValue = intValue
        stringValue = String(intValue)
    }
}

extension KeyedDecodingContainer {

    private
    func decode(_ type: [Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [Any] {
        var values = try nestedUnkeyedContainer(forKey: key)
        return try values.decode(type)
    }

    private
    func decode(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [String: Any] {
        try nestedContainer(keyedBy: AnyCodingKey.self, forKey: key).decode(type)
    }

    func decode(_ type: [String: Any].Type) throws -> [String: Any] {
        var dictionary: [String: Any] = [:]
        for key in allKeys {
            if try decodeNil(forKey: key) {
                dictionary[key.stringValue] = NSNull()
            } else if let bool = try? decode(Bool.self, forKey: key) {
                dictionary[key.stringValue] = bool
            } else if let string = try? decode(String.self, forKey: key) {
                dictionary[key.stringValue] = string
            } else if let int = try? decode(Int.self, forKey: key) {
                dictionary[key.stringValue] = int
            } else if let double = try? decode(Double.self, forKey: key) {
                dictionary[key.stringValue] = double
            } else if let dict = try? decode([String: Any].self, forKey: key) {
                dictionary[key.stringValue] = dict
            } else if let array = try? decode([Any].self, forKey: key) {
                dictionary[key.stringValue] = array
            }
        }
        return dictionary
    }
}

extension UnkeyedDecodingContainer {
    mutating func decode(_ type: [Any].Type) throws -> [Any] {
        var elements: [Any] = []
        while !isAtEnd {
            if try decodeNil() {
                elements.append(NSNull())
            } else if let int = try? decode(Int.self) {
                elements.append(int)
            } else if let bool = try? decode(Bool.self) {
                elements.append(bool)
            } else if let double = try? decode(Double.self) {
                elements.append(double)
            } else if let string = try? decode(String.self) {
                elements.append(string)
            } else if let values = try? nestedContainer(keyedBy: AnyCodingKey.self),
                let element = try? values.decode([String: Any].self) {
                elements.append(element)
            } else if var values = try? nestedUnkeyedContainer(),
                let element = try? values.decode([Any].self) {
                elements.append(element)
            }
        }
        return elements
    }
}

วิธีการแก้

struct DecodableDictionary: Decodable {
    typealias Value = [String: Any]
    let dictionary: Value?
    init(from decoder: Decoder) throws {
        dictionary = try? decoder.container(keyedBy: AnyCodingKey.self).decode(Value.self)
    }
}

การใช้งาน

struct Model: Decodable {
    let num: Double?
    let flag: Bool?
    let dict: DecodableDictionary?
    let dict2: DecodableDictionary?
    let dict3: DecodableDictionary?
}

let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print(object.dict?.dictionary)      // prints [String: Any]
print(object.dict2?.dictionary)     // prints nil
print(object.dict3?.dictionary)     // prints nil

-1
extension ViewController {

    func swiftyJson(){
        let url = URL(string: "https://itunes.apple.com/search?term=jack+johnson")
        //let url = URL(string: "http://makani.bitstaging.in/api/business/businesses_list")

        Alamofire.request(url!, method: .get, parameters: nil).responseJSON { response in
            var arrayIndexes = [IndexPath]()
            switch(response.result) {
            case .success(_):

                let data = response.result.value as! [String : Any]

                if let responseData =  Mapper<DataModel>().map(JSON: data) {
                    if responseData.results!.count > 0{
                        self.arrayExploreStylistList = []
                    }
                    for i in 0..<responseData.results!.count{
                        arrayIndexes.append(IndexPath(row: self.arrayExploreStylistList.count + i, section: 0))
                    }
                    self.arrayExploreStylistList.append(contentsOf: responseData.results!)

                    print(arrayIndexes.count)

                }

                //                    if let arrNew = data["results"] as? [[String : Any]]{
                //                        let jobData = Mapper<DataModel>().mapArray(JSONArray: arrNew)
                //                        print(jobData)
                //                        self.datamodel = jobData
                //                    }
                self.tblView.reloadData()
                break

            case .failure(_):
                print(response.result.error as Any)
                break

            }
        }

    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.