Bir sözlüğe kodlamak için Swift's Codable'ı nasıl kullanabilirim?


Yanıtlar:


232

Etrafınızdaki verilerin biraz değişmesini önemsemiyorsanız, bunun gibi bir şey kullanabilirsiniz:

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

Veya isteğe bağlı bir değişken

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

Varsayarsak Foouyan, Codableya gerçekten Encodableo zaman bunu yapabilirsiniz.

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

Diğer yöne gitmek istiyorsanız ( init(any)), bu Init'e bir sözlük / dizi ile Kodlanabilir'e uygun bir nesne bakın.


İsteğe bağlı değişken uygulaması harika, temiz, hızlı ve guard let ifadeleri için mükemmeldir. API çağrılarını gerçekten temizler.
Demir John Bonney

22

Burada basit bir uygulaması şunlardır DictionaryEncoder/ DictionaryDecodersarılan JSONEncoder, JSONDecoderve JSONSerializationayrıca stratejilerini deşifre / kodlayan şekilde ele aldığını, ...

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

Kullanım benzerdir JSONEncoder/ JSONDecoder

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

ve

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

Kolaylık sağlamak için, hepsini bir depoya koydum ...  https://github.com/ashleymills/SwiftDictionaryCoding


Çok teşekkürler!, Alternatif kalıtım kullanmak olacaktır, ancak arayan site, farklı dönüş türlerinin 2 işlevi olacağı için türü bir sözlük olarak çıkaramaz.
user1046037

17

CodableFirebase adında bir kitaplık oluşturdum ve ilk amacı onu Firebase Veritabanı ile kullanmaktı, ancak aslında ihtiyacınız olan şeyi yapıyor: bir sözlük veya tıpkı in gibi başka bir tür oluşturur, JSONDecoderancak burada çift dönüşümü yapmanız gerekmez diğer cevaplarda yaptığınız gibi. Yani şöyle bir şeye benzeyecektir:

import CodableFirebase

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

7

Bunun en iyi yol olup olmadığından emin değilim ama kesinlikle şöyle bir şey yapabilirsiniz:

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
Bu sadece aynı türden tüm özelliklere sahip yapılar için işe
yarar

1
Az önce "let dict = JSONDecoder () deneyin. Decode ([String: Int] .self, from: JSONEncoder (). Encode (foo))" denedim ve "Dictionary <String, Any> kodunun çözülmesi bekleniyordu ancak bir dizi yerine. " yardım edebilir
misiniz

6

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


6

Bunu yapmanın yerleşik bir yolu yok. Yukarıda yanıtlandığı gibi , performans sorununuz yoksa JSONEncoder+ JSONSerializationuygulamasını kabul edebilirsiniz .

Ancak, bir kodlayıcı / kod çözücü nesnesi sağlamak için standart kitaplığın yolunu tercih ederim.

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

Aşağıdaki kodla deneyebilirsiniz:

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)

Burada örneği kısaltmak için zorla çalışıyorum. Üretim kodunda hataları uygun şekilde işlemelisiniz.


4

Bazı projelerde hızlı yansımayı kullandım. Ancak dikkatli olun, iç içe kodlanabilir nesneler orada da eşlenmez.

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

2

CodableJSON / Plists / her neyse, herhangi bir şekilde vurma niyeti olmadan, sözlüklere / sözlüklerden kodlama yapmak için kullanabilmenin kesinlikle bir değeri olduğunu düşünüyorum . Size sadece bir sözlüğü geri veren veya bir sözlük bekleyen çok sayıda API var ve bunları Swift yapıları veya nesneleriyle sonsuz ortak kod yazmak zorunda kalmadan kolayca değiştirebilmek güzel.

Foundation JSONEncoder.swift kaynağına dayalı bazı kodlarla oynuyorum (aslında sözlük kodlama / kod çözmeyi dahili olarak uyguluyor, ancak dışa aktarmıyor).

Kod burada bulunabilir: https://github.com/elegantchaos/DictionaryCoding

Hala oldukça kaba, ancak biraz genişlettim, böylece örneğin, kod çözme sırasında eksik değerleri varsayılanlarla doldurabilir.


2

Ben değiştirdiniz PropertyListEncoder basitçe ikili biçime sözlükten nihai seri kaldırarak, bir DictionaryEncoder içine Swift projesinden. Sen de aynısını yapabilirsin veya kodumu buradan alabilirsin

Şu şekilde kullanılabilir:

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

0

Ben hızlı yazdım özünü (kodlanabilir protokolünü kullanarak değil) bu işlemek için. Dikkatli olun, herhangi bir değeri yazıp kontrol etmez ve kodlanabilir değerler üzerinde özyinelemeli olarak çalışmaz.

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

Bunu Codable'da yapmanın doğrudan bir yolu yoktur. Yapınız için Kodlanabilir / Kod Çözülebilir protokolü uygulamanız gerekir. Örneğiniz için aşağıdaki gibi yazmanız gerekebilir

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

Kod çözme ve kodlamayı kolaylaştırmak için burada https://github.com/levantAJ/AnyCodable bir kapsül yaptım ve [String: Any][Any]

pod 'DynamicCodable', '1.0'

Ve deşifre edebilir, kodlayabilir [String: Any]ve[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)
    }
}


0

SwiftyJSON kullanıyorsanız , bunun gibi bir şey yapabilirsiniz:

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

Not: Ayrıca bu sözlüğü geçebilir parametersiçin Alamofire istekleri.


0

İşte protokol tabanlı bir çözüm:

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

Ve işte nasıl kullanılacağı:

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

İşte sözlük -> nesne. Hızlı 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

Bir düşünün, genel durumda sorunun bir cevabı yok, çünkü Encodableörnek bir sözlüğe serileştirilemeyen bir şey olabilir, örneğin bir dizi:

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

Bunun dışında çerçeve olarak benzer bir şey yazdım .


İtiraf etmeliyim ki bunun neden olumsuz oy verildiğini hala anlamıyorum :–) Uyarı doğru değil mi? Veya çerçeve yararlı değil mi?
zoul
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.