Làm cách nào để sử dụng Swift's Codable để mã hóa thành từ điển?


110

Tôi có một cấu trúc triển khai Swift 4's Codable. Có cách nào đơn giản được tích hợp sẵn để mã hóa cấu trúc đó thành từ điển không?

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

Câu trả lời:


231

Nếu bạn không bận tâm về một chút thay đổi dữ liệu xung quanh, bạn có thể sử dụng một cái gì đó như sau:

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

Hoặc một biến thể tùy chọn

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

Giả sử Footuân theo Codablehoặc thực sự Encodablethì bạn có thể làm điều này.

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

Nếu bạn muốn đi theo cách khác ( init(any)), hãy xem phần này Init một đối tượng tuân theo Codable với từ điển / mảng


22

Dưới đây là hiện thực đơn giản DictionaryEncoder/ DictionaryDecoderbọc đó JSONEncoder, JSONDecoderJSONSerialization, mà còn xử lý mã hóa / giải mã chiến lược ...

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

Cách sử dụng tương tự như JSONEncoder/ JSONDecoder

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

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

Để thuận tiện, tôi đã đưa tất cả điều này vào một kho lưu trữ…  https://github.com/ashleymills/SwiftDictionaryCoding


Cảm ơn rất nhiều !, phương pháp thay thế sẽ là sử dụng kế thừa nhưng trang web gọi sẽ không thể suy ra loại dưới dạng từ điển vì sẽ có 2 hàm thuộc các loại trả về khác nhau.
user1046037 24/02/19

17

Tôi đã tạo một thư viện có tên CodableFirebase và mục đích ban đầu là sử dụng nó với Cơ sở dữ liệu Firebase, nhưng nó thực sự có những gì bạn cần: nó tạo từ điển hoặc bất kỳ loại nào khác giống như trong JSONDecodernhưng bạn không cần thực hiện chuyển đổi kép ở đây như bạn làm trong các câu trả lời khác. Vì vậy, nó sẽ giống như sau:

import CodableFirebase

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

7

Tôi không chắc đó có phải là cách tốt nhất hay không nhưng bạn chắc chắn có thể làm điều gì đó như:

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
Điều này sẽ chỉ làm việc cho các cấu trúc với tất cả các thuộc tính của cùng loại
Leo Dabus

1
Tôi vừa thử "let dict = try JSONDecoder (). Decode ([String: Int] .self, from: JSONEncoder (). Encode (foo))" và tôi nhận được "Dự kiến ​​sẽ giải mã từ điển <String, Any> nhưng tìm thấy mảng thay thế. " u có thể giúp pls
Itan hant

6

let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]


6

Không có cách nào được xây dựng để làm điều đó. Như đã trả lời ở trên nếu bạn không có vấn đề gì về hiệu suất thì bạn có thể chấp nhận việc triển khai JSONEncoder+ JSONSerialization.

Nhưng tôi muốn đi theo cách của thư viện tiêu chuẩn để cung cấp một đối tượng bộ mã hóa / giải mã.

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

Bạn có thể thử nó với mã sau:

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)

Tôi đang cố gắng làm cho ví dụ ngắn hơn. Trong mã sản xuất, bạn nên xử lý các lỗi một cách thích hợp.


4

Trong một số dự án, tôi đã sử dụng phản xạ nhanh. Nhưng hãy cẩn thận, các đối tượng codable lồng nhau, không được ánh xạ cũng ở đó.

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

2

Tôi chắc chắn nghĩ rằng có một số giá trị khi có thể sử dụng Codableđể mã hóa vào / từ từ điển, mà không có ý định nhấn JSON / Plists / bất cứ thứ gì. Có rất nhiều API chỉ cung cấp cho bạn một từ điển hoặc mong đợi một từ điển và thật tuyệt khi có thể trao đổi chúng dễ dàng với các cấu trúc hoặc đối tượng Swift mà không cần phải viết vô số mã lệnh soạn sẵn.

Tôi đã thử với một số mã dựa trên nguồn Foundation JSONEncoder.swift (thực sự triển khai mã hóa / giải mã từ điển bên trong, nhưng không xuất nó).

Bạn có thể tìm thấy mã tại đây: https://github.com/elegantchaos/DictionaryCoding

Nó vẫn còn khá thô, nhưng tôi đã mở rộng nó một chút để chẳng hạn, nó có thể điền vào các giá trị bị thiếu với giá trị mặc định khi giải mã.


2

Tôi đã sửa đổi PropertyListEncoder từ dự án Swift thành DictionaryEncoder, đơn giản bằng cách xóa tuần tự hóa cuối cùng khỏi từ điển sang định dạng nhị phân. Bạn có thể tự làm điều tương tự hoặc bạn có thể lấy mã của tôi từ đây

Nó có thể được sử dụng như thế này:

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

0

Tôi đã viết một ý chính nhanh để xử lý vấn đề này (không sử dụng giao thức Codable). Hãy cẩn thận, nó không nhập kiểm tra bất kỳ giá trị nào và không hoạt động đệ quy trên các giá trị có thể mã hóa.

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

Không có cách nào dễ dàng để làm điều này trong Codable. Bạn cần triển khai giao thức Có thể mã hóa / Có thể giải mã cho cấu trúc của mình. Đối với ví dụ của bạn, bạn có thể cần viết như dưới đây

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

Tôi đã tạo một nhóm ở đây https://github.com/levantAJ/AnyCodable để hỗ trợ giải mãmã hóa [String: Any][Any]

pod 'DynamicCodable', '1.0'

Và bạn có thể giải mã và mã hóa [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
Ví dụ bạn không hiển thị như thế nào để giải quyết vấn đề
Simon Moshenko

0

Nếu đang sử dụng SwiftyJSON , bạn có thể làm như sau:

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

Lưu ý: Bạn cũng có thể chuyển từ điển này parameterscho các yêu cầu Alamofire .


0

Đây là một giải pháp dựa trên giao thức:

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

Và đây là cách sử dụng nó:

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

Đây là từ điển -> đối tượng. Swift 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
        }
    }
}

-5

Hãy nghĩ về nó, câu hỏi không có câu trả lời trong trường hợp chung, vì Encodabletrường hợp có thể là thứ gì đó không thể tuần tự hóa vào từ điển, chẳng hạn như một mảng:

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

Ngoài ra, tôi đã viết một cái gì đó tương tự như một khuôn khổ .


Tôi phải thừa nhận rằng tôi vẫn không hiểu tại sao điều này bị phản đối :–) Có phải lời báo trước không đúng? Hoặc khuôn khổ không hữu ích?
zoul
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.