Câu trả lời:
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ử Foo
tuân theo Codable
hoặc thực sự Encodable
thì 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
Dưới đây là hiện thực đơn giản DictionaryEncoder
/ DictionaryDecoder
bọc đó JSONEncoder
, JSONDecoder
và JSONSerialization
, 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)
và
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
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 JSONDecoder
như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)
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)
let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]
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.
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) })
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ã.
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
}
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)
}
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)
}
}
Tôi đã tạo một nhóm ở đây https://github.com/levantAJ/AnyCodable để hỗ trợ giải mã và mã hóa [String: Any]
và[Any]
pod 'DynamicCodable', '1.0'
Và bạn có thể giải mã và mã hóa [String: Any]
và[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)
}
}
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
parameters
cho các yêu cầu Alamofire .
Đâ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)\"")
}
Đâ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
}
}
}
Hãy nghĩ về nó, câu hỏi không có câu trả lời trong trường hợp chung, vì Encodable
trườ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ổ .