Programing

Swift의 Codable을 사용하여 사전으로 인코딩하려면 어떻게해야합니까?

lottogame 2020. 9. 2. 20:24
반응형

Swift의 Codable을 사용하여 사전으로 인코딩하려면 어떻게해야합니까?


Swift 4를 구현하는 구조체가 Codable있습니다. 해당 구조체를 사전으로 인코딩하는 간단한 기본 제공 방법이 있습니까?

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

약간의 데이터 이동에 신경 쓰지 않는다면 다음과 같이 사용할 수 있습니다.

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 init an object conforming to Codable with a dictionary / array를 살펴보십시오.


여기서 간단하게 구현되어 DictionaryEncoder/ DictionaryDecoder그 포장 JSONEncoder, JSONDecoderJSONSerialization도 전략을 디코딩 / 인코딩 처리 즉, ...

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)

편의를 위해이 모든 것을 저장소에 넣었습니다.  https://github.com/ashleymills/SwiftDictionaryCoding


저는 CodableFirebase 라는 라이브러리를 만들었 으며 초기 목적은 Firebase 데이터베이스와 함께 사용하는 것이었지만 실제로 JSONDecoder필요한 작업을 수행합니다. 에서와 같이 사전 또는 다른 유형을 생성 하지만 여기서 이중 변환을 수행 할 필요는 없습니다. 다른 답변 에서처럼. 따라서 다음과 같이 보일 것입니다.

import CodableFirebase

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

이것이 최선의 방법인지 확실하지 않지만 확실히 다음과 같이 할 수 있습니다.

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)

그렇게 할 수있는 방법은 없습니다. 위에서 답변 한대로 성능 문제가없는 경우 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)
    }
}

You can try it with following code:

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)

I am force-trying here to make the example shorter. In production code you should handle the errors appropriately.


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


In some project, i'm used the swift reflection. But be careful, nested codable objects, are not mapped also there.

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

I definitely think that there's some value in just being able to use Codable to encode to/from dictionaries, without the intention of ever hitting JSON/Plists/whatever. There are plenty of APIs which just give you back a dictionary, or expect a dictionary, and it's nice to be able to interchange them easily with Swift structs or objects, without having to write endless boilerplate code.

I've been playing round with some code based on the Foundation JSONEncoder.swift source (which actually does implement dictionary encoding/decoding internally, but doesn't export it).

The code can be found here: https://github.com/elegantchaos/DictionaryCoding

It's still quite rough, but I've expanded it a bit so that, for example, it can fill in missing values with defaults when decoding.


I have modified the PropertyListEncoder from the Swift project into a DictionaryEncoder, simply by removing the final serialisation from dictionary into binary format. You can do the same yourself, or you can take my code from here

It can be used like this:

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

I wrote a quick gist to handle this (not using the Codable protocol). Be careful, it doesn't type-check any values and doesn't work recursively on values that are encodable.

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

There no straight forward way of doing this in Codable. You need to implement Encodable/Decodable protocol for your struct. For your example, you might need to write as below

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

I have made a pod here https://github.com/levantAJ/AnyCodable to facilitate decode and encode [String: Any] and [Any]

pod 'DynamicCodable', '1.0'

And you are able to decode & encode [String: Any] and [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)
    }
}

If you're using SwiftyJSON, you could do something like this:

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

Note: You can also pass this dictionary as parameters to Alamofire requests.


Come to think of it, the question does not have an answer in the general case, since the Encodable instance may be something not serializable into a dictionary, such as an array:

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

Other than that, I have written something similar as a framework.

참고URL : https://stackoverflow.com/questions/45209743/how-can-i-use-swift-s-codable-to-encode-into-a-dictionary

반응형