ฉันจะใช้ Swift's Codable เพื่อเข้ารหัสลงในพจนานุกรมได้อย่างไร


110

ฉันมีโครงสร้างที่ดำเนิน Swift Codable4 มีวิธีง่ายๆในการเข้ารหัสโครงสร้างนั้นในพจนานุกรมหรือไม่?

let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]

คำตอบ:


234

หากคุณไม่สนใจการเปลี่ยนแปลงข้อมูลรอบตัวคุณสามารถใช้สิ่งนี้:

extension Encodable {
  func asDictionary() throws -> [String: Any] {
    let data = try JSONEncoder().encode(self)
    guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
      throw NSError()
    }
    return dictionary
  }
}

หรือตัวแปรเสริม

extension Encodable {
  var dictionary: [String: Any]? {
    guard let data = try? JSONEncoder().encode(self) else { return nil }
    return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
  }
}

สมมติว่าเป็นFooไปตามCodableหรือจริงๆEncodableแล้วคุณสามารถทำได้

let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary

หากคุณต้องการไปทางอื่น ( init(any)) ให้ดูที่Initนี้วัตถุที่สอดคล้องกับ Codable ด้วยพจนานุกรม / อาร์เรย์


การใช้งาน var ที่เป็นทางเลือกนั้นยอดเยี่ยมสะอาดรวดเร็วและสมบูรณ์แบบสำหรับงบการป้องกัน ล้างการเรียก API จริงๆ
Iron John Bonney

23

ต่อไปนี้คือการใช้งานDictionaryEncoder/ DictionaryDecoderที่ห่ออย่างJSONEncoderง่ายJSONDecoderและJSONSerializationยังจัดการกลยุทธ์การเข้ารหัส / ถอดรหัส ...

class DictionaryEncoder {

    private let encoder = JSONEncoder()

    var dateEncodingStrategy: JSONEncoder.DateEncodingStrategy {
        set { encoder.dateEncodingStrategy = newValue }
        get { return encoder.dateEncodingStrategy }
    }

    var dataEncodingStrategy: JSONEncoder.DataEncodingStrategy {
        set { encoder.dataEncodingStrategy = newValue }
        get { return encoder.dataEncodingStrategy }
    }

    var nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy {
        set { encoder.nonConformingFloatEncodingStrategy = newValue }
        get { return encoder.nonConformingFloatEncodingStrategy }
    }

    var keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy {
        set { encoder.keyEncodingStrategy = newValue }
        get { return encoder.keyEncodingStrategy }
    }

    func encode<T>(_ value: T) throws -> [String: Any] where T : Encodable {
        let data = try encoder.encode(value)
        return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
    }
}

class DictionaryDecoder {

    private let decoder = JSONDecoder()

    var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy {
        set { decoder.dateDecodingStrategy = newValue }
        get { return decoder.dateDecodingStrategy }
    }

    var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy {
        set { decoder.dataDecodingStrategy = newValue }
        get { return decoder.dataDecodingStrategy }
    }

    var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy {
        set { decoder.nonConformingFloatDecodingStrategy = newValue }
        get { return decoder.nonConformingFloatDecodingStrategy }
    }

    var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy {
        set { decoder.keyDecodingStrategy = newValue }
        get { return decoder.keyDecodingStrategy }
    }

    func decode<T>(_ type: T.Type, from dictionary: [String: Any]) throws -> T where T : Decodable {
        let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
        return try decoder.decode(type, from: data)
    }
}

การใช้งานคล้ายกับJSONEncoder/ JSONDecoder

let dictionary = try DictionaryEncoder().encode(object)

และ

let object = try DictionaryDecoder().decode(Object.self, from: dictionary)

เพื่อความสะดวกฉันได้ใส่ทั้งหมดนี้ไว้ใน repo ...  https://github.com/ashleymills/SwiftDictionaryCoding


ขอบคุณมาก! ทางเลือกอื่นคือการใช้การสืบทอด แต่ไซต์การโทรไม่สามารถอนุมานประเภทเป็นพจนานุกรมได้เนื่องจากจะมีฟังก์ชันการส่งคืน 2 ประเภทที่แตกต่างกัน
user1046037

17

ฉันได้สร้างไลบรารีชื่อCodableFirebaseและจุดประสงค์เริ่มต้นคือการใช้กับฐานข้อมูล Firebase แต่มันทำในสิ่งที่คุณต้องการจริงๆ: สร้างพจนานุกรมหรือประเภทอื่น ๆ เช่นเดียวกับในJSONDecoderแต่คุณไม่จำเป็นต้องทำการแปลงซ้ำที่นี่ เช่นเดียวกับที่คุณทำในคำตอบอื่น ๆ ดังนั้นมันจะมีลักษณะดังนี้:

import CodableFirebase

let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)

7

ฉันไม่แน่ใจว่าเป็นวิธีที่ดีที่สุดหรือไม่ แต่คุณสามารถทำได้อย่างแน่นอนเช่น:

struct Foo: Codable {
    var a: Int
    var b: Int

    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
}

let foo = Foo(a: 1, b: 2)
let dict = try JSONDecoder().decode([String: Int].self, from: JSONEncoder().encode(foo))
print(dict)

8
สิ่งนี้จะใช้ได้เฉพาะกับโครงสร้างที่มีคุณสมบัติเดียวกันทั้งหมด
Leo Dabus

1
ฉันเพิ่งลอง "let dict = ลอง JSONDecoder (). ถอดรหัส ([String: Int] .self จาก: JSONEncoder (). encode (foo))" และฉันได้รับ "คาดว่าจะถอดรหัสพจนานุกรม <String, Any> แต่พบว่า อาร์เรย์แทน " คุณช่วยกรุณา
Itan Hant


6

ไม่มีในตัวที่จะทำเช่นนั้น ตามคำตอบข้างต้นหากคุณไม่มีปัญหาด้านประสิทธิภาพคุณสามารถยอมรับการใช้งานJSONEncoder+ JSONSerializationได้

แต่ฉันอยากจะใช้วิธีของไลบรารีมาตรฐานในการจัดหาอ็อบเจ็กต์ตัวเข้ารหัส / ตัวถอดรหัส

class DictionaryEncoder {
    private let jsonEncoder = JSONEncoder()

    /// Encodes given Encodable value into an array or dictionary
    func encode<T>(_ value: T) throws -> Any where T: Encodable {
        let jsonData = try jsonEncoder.encode(value)
        return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
    }
}

class DictionaryDecoder {
    private let jsonDecoder = JSONDecoder()

    /// Decodes given Decodable type from given array or dictionary
    func decode<T>(_ type: T.Type, from json: Any) throws -> T where T: Decodable {
        let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
        return try jsonDecoder.decode(type, from: jsonData)
    }
}

คุณสามารถลองใช้รหัสต่อไปนี้:

struct Computer: Codable {
    var owner: String?
    var cpuCores: Int
    var ram: Double
}

let computer = Computer(owner: "5keeve", cpuCores: 8, ram: 4)
let dictionary = try! DictionaryEncoder().encode(computer)
let decodedComputer = try! DictionaryDecoder().decode(Computer.self, from: dictionary)

ฉันกำลังพยายามที่จะทำให้ตัวอย่างสั้นลง ในรหัสการผลิตคุณควรจัดการกับข้อผิดพลาดอย่างเหมาะสม


4

ในบางโครงการฉันใช้การสะท้อนที่รวดเร็ว แต่ระวังวัตถุที่เข้ารหัสที่ซ้อนกันจะไม่ถูกแมปไว้ที่นั่นด้วย

let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })

2

แน่นอนฉันคิดว่ามันมีประโยชน์ในการใช้งาน Codableเพื่อเข้ารหัส / จากพจนานุกรมโดยไม่ต้องกดปุ่ม JSON / Plists / อะไรก็ตาม มี API มากมายที่ให้พจนานุกรมคืนหรือคาดว่าจะได้พจนานุกรมและเป็นเรื่องดีที่สามารถแลกเปลี่ยนกับ Swift โครงสร้างหรือวัตถุได้อย่างง่ายดายโดยไม่ต้องเขียนโค้ดสำเร็จรูปไม่รู้จบ

ฉันเล่นรอบกับโค้ดบางส่วนตามแหล่งที่มา Foundation JSONEncoder.swift (ซึ่งจริงๆแล้วใช้การเข้ารหัส / ถอดรหัสพจนานุกรมภายใน แต่ไม่ได้ส่งออก)

สามารถดูรหัสได้ที่นี่: https://github.com/elegantchaos/DictionaryCoding

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


2

ฉันได้แก้ไขPropertyListEncoderจากโครงการ Swift เป็น DictionaryEncoder เพียงแค่ลบการทำให้เป็นอนุกรมสุดท้ายจากพจนานุกรมเป็นรูปแบบไบนารี คุณสามารถทำได้ด้วยตัวเองหรือคุณสามารถใช้รหัสของฉันจากที่นี่

สามารถใช้งานได้ดังนี้:

do {
    let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
    // handle error
}

0

ฉันเขียนสาระสำคัญสั้น ๆเพื่อจัดการสิ่งนี้ (ไม่ใช้โปรโตคอล Codable) ระวังอย่าพิมพ์ - ตรวจสอบค่าใด ๆ และไม่ทำงานซ้ำกับค่าที่เข้ารหัสได้

class DictionaryEncoder {
    var result: [String: Any]

    init() {
        result = [:]
    }

    func encode(_ encodable: DictionaryEncodable) -> [String: Any] {
        encodable.encode(self)
        return result
    }

    func encode<T, K>(_ value: T, key: K) where K: RawRepresentable, K.RawValue == String {
        result[key.rawValue] = value
    }
}

protocol DictionaryEncodable {
    func encode(_ encoder: DictionaryEncoder)
}

0

ไม่มีวิธีตรงไปตรงมาในการทำเช่นนี้ใน Codable คุณต้องใช้โปรโตคอลที่เข้ารหัส / ถอดรหัสได้สำหรับโครงสร้างของคุณ ตัวอย่างเช่นคุณอาจต้องเขียนดังนี้

typealias EventDict = [String:Int]

struct Favorite {
    var all:EventDict
    init(all: EventDict = [:]) {
        self.all = all
    }
}

extension Favorite: Encodable {
    struct FavoriteKey: CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int? { return nil }
        init?(intValue: Int) { return nil }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: FavoriteKey.self)

        for eventId in all {
            let nameKey = FavoriteKey(stringValue: eventId.key)!
            try container.encode(eventId.value, forKey: nameKey)
        }
    }
}

extension Favorite: Decodable {

    public init(from decoder: Decoder) throws {
        var events = EventDict()
        let container = try decoder.container(keyedBy: FavoriteKey.self)
        for key in container.allKeys {
            let fav = try container.decode(Int.self, forKey: key)
            events[key.stringValue] = fav
        }
        self.init(all: events)
    }
}

0

ฉันได้สร้างพ็อดที่นี่https://github.com/levantAJ/AnyCodableเพื่ออำนวยความสะดวกในการถอดรหัสและเข้ารหัส [String: Any]และ[Any]

pod 'DynamicCodable', '1.0'

และคุณสามารถถอดรหัสและเข้ารหัส[String: Any]และ[Any]

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

1
ตัวอย่างของคุณไม่ได้แสดงวิธีแก้ปัญหา
Simon Moshenko

0

นี่คือโซลูชันที่ใช้โปรโตคอล:

protocol DictionaryEncodable {
    func encode() throws -> Any
}

extension DictionaryEncodable where Self: Encodable {
    func encode() throws -> Any {
        let jsonData = try JSONEncoder().encode(self)
        return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
    }
}

protocol DictionaryDecodable {
    static func decode(_ dictionary: Any) throws -> Self
}

extension DictionaryDecodable where Self: Decodable {
    static func decode(_ dictionary: Any) throws -> Self {
        let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
        return try JSONDecoder().decode(Self.self, from: jsonData)
    }
}

typealias DictionaryCodable = DictionaryEncodable & DictionaryDecodable

และนี่คือวิธีการใช้งาน:

class AClass: Codable, DictionaryCodable {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

struct AStruct: Codable, DictionaryEncodable, DictionaryDecodable {
    
    var name: String
    var age: Int
}

let aClass = AClass(name: "Max", age: 24)

if let dict = try? aClass.encode(), let theClass = try? AClass.decode(dict) {
    print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theClass.name), age: \(theClass.age)\"")
}

let aStruct = AStruct(name: "George", age: 30)

if let dict = try? aStruct.encode(), let theStruct = try? AStruct.decode(dict) {
    print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theStruct.name), age: \(theStruct.age)\"")
}

0

นี่คือพจนานุกรม -> วัตถุ สวิฟต์ 5.

extension Dictionary where Key == String, Value: Any {

    func object<T: Decodable>() -> T? {
        if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
            return try? JSONDecoder().decode(T.self, from: data)
        } else {
            return nil
        }
    }
}

0

หลังจากการวิจัยเราพบว่าหากเราใช้คีย์เวิร์ด Any ในคลาสซึ่งสืบทอดมาจาก Codable & Decodable จะทำให้เกิดข้อผิดพลาด ดังนั้นหากคุณต้องการใช้ผู้ใช้พจนานุกรมกับประเภทของข้อมูลที่มาจากเซิร์ฟเวอร์ ตัวอย่างเช่นเซิร์ฟเวอร์กำลังส่งพจนานุกรมประเภท [String: Int] จากนั้นใช้ [String: Int] หากคุณจะลอง [String: Any] ก็จะไม่ทำงาน


-1

หากคุณใช้SwiftyJSONคุณสามารถทำสิ่งนี้ได้:

JSON(data: JSONEncoder().encode(foo)).dictionaryObject

หมายเหตุ:คุณยังส่งพจนานุกรมนี้parametersไปยังคำขอของAlamofireได้อีกด้วย


-5

ลองคิดดูคำถามไม่มีคำตอบในกรณีทั่วไปเนื่องจากEncodableอินสแตนซ์อาจเป็นสิ่งที่ไม่สามารถต่อเนื่องกันได้ในพจนานุกรมเช่นอาร์เรย์:

let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"

อื่น ๆ กว่าที่ฉันได้เขียนสิ่งที่คล้ายกันเป็นกรอบ


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