Jawaban:
Jika Anda tidak keberatan dengan sedikit pergeseran data di sekitar Anda, Anda dapat menggunakan sesuatu seperti ini:
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
}
}
Atau varian opsional
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] }
}
}
Dengan asumsi Foo
sesuai dengan Codable
atau benar-benar Encodable
maka Anda dapat melakukan ini.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
Jika Anda ingin pergi ke arah lain ( init(any)
), lihat objek Init ini yang sesuai dengan Codable dengan kamus / larik
Berikut adalah implementasi sederhana dari DictionaryEncoder
/ DictionaryDecoder
that wrap JSONEncoder
, JSONDecoder
dan JSONSerialization
, yang juga menangani strategi encoding / decoding…
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)
}
}
Penggunaannya mirip dengan JSONEncoder
/ JSONDecoder
…
let dictionary = try DictionaryEncoder().encode(object)
dan
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
Untuk kenyamanan, saya telah menempatkan ini semua dalam repo… https://github.com/ashleymills/SwiftDictionaryCoding
Saya telah membuat pustaka bernama CodableFirebase dan tujuan awalnya adalah menggunakannya dengan Firebase Database, tetapi sebenarnya ini melakukan apa yang Anda butuhkan: membuat kamus atau jenis lain seperti di JSONDecoder
tetapi Anda tidak perlu melakukan konversi ganda di sini seperti yang Anda lakukan di jawaban lain. Jadi akan terlihat seperti ini:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
Saya tidak yakin apakah itu cara terbaik tetapi Anda pasti dapat melakukan sesuatu seperti:
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]
Tidak ada cara bawaan untuk melakukan itu. Seperti yang dijawab di atas, jika Anda tidak memiliki masalah kinerja, maka Anda dapat menerima JSONEncoder
+ JSONSerialization
implementasi.
Tapi saya lebih suka menggunakan cara perpustakaan standar untuk menyediakan objek encoder / decoder.
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)
}
}
Anda dapat mencobanya dengan kode berikut:
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)
Di sini saya mencoba paksa untuk mempersingkat contoh. Dalam kode produksi, Anda harus menangani kesalahan dengan tepat.
Dalam beberapa proyek, saya menggunakan refleksi cepat. Tapi hati-hati, objek codable bersarang, jangan dipetakan juga di sana.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
Saya benar-benar berpikir bahwa ada beberapa nilai dalam hanya dapat digunakan Codable
untuk menyandikan ke / dari kamus, tanpa niat untuk memukul JSON / Plists / apa pun. Ada banyak API yang hanya mengembalikan kamus, atau mengharapkan kamus, dan senang dapat menukarnya dengan mudah dengan struct atau objek Swift, tanpa harus menulis kode boilerplate tanpa akhir.
Saya telah bermain-main dengan beberapa kode berdasarkan sumber Foundation JSONEncoder.swift (yang sebenarnya mengimplementasikan encoding / decoding kamus secara internal, tetapi tidak mengekspornya).
Kode tersebut dapat ditemukan di sini: https://github.com/elegantchaos/DictionaryCoding
Ini masih cukup kasar, tetapi saya telah mengembangkannya sedikit sehingga, misalnya, dapat mengisi nilai yang hilang dengan default saat mendekode.
Saya telah memodifikasi PropertyListEncoder dari proyek Swift menjadi DictionaryEncoder, cukup dengan menghapus serialisasi terakhir dari kamus ke dalam format biner. Anda dapat melakukan hal yang sama sendiri, atau Anda dapat mengambil kode saya dari sini
Ini bisa digunakan seperti ini:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
Saya menulis inti singkat untuk menangani ini (tidak menggunakan protokol Codable). Hati-hati, ini tidak memeriksa ketikan nilai apa pun dan tidak berfungsi secara rekursif pada nilai yang dapat dienkode.
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)
}
Tidak ada cara langsung untuk melakukan ini di Codable. Anda perlu menerapkan protokol Encodable / Decodable untuk struct Anda. Untuk contoh Anda, Anda mungkin perlu menulis seperti di bawah ini
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)
}
}
Saya telah membuat pod di sini https://github.com/levantAJ/AnyCodable untuk memfasilitasi decode dan encode [String: Any]
dan[Any]
pod 'DynamicCodable', '1.0'
Dan Anda dapat memecahkan kode & menyandikan [String: Any]
dan[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)
}
}
Jika Anda menggunakan SwiftyJSON , Anda dapat melakukan sesuatu seperti ini:
JSON(data: JSONEncoder().encode(foo)).dictionaryObject
Catatan: Anda juga dapat meneruskan kamus ini
parameters
untuk permintaan Alamofire .
Berikut adalah solusi berbasis protokol:
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
Dan inilah cara menggunakannya:
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)\"")
}
Ini kamus -> objek. Cepat 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
}
}
}
Kalau dipikir-pikir, pertanyaannya tidak memiliki jawaban dalam kasus umum, karena Encodable
instance tersebut mungkin sesuatu yang tidak dapat diserialkan ke dalam kamus, seperti array:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
Selain itu, saya telah menulis sesuatu yang mirip sebagai kerangka kerja .