[swift] 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)


답변

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


답변

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

다음 코드로 시도해 볼 수 있습니다.

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)

나는 예제를 더 짧게 만들기 위해 여기에서 강제로 노력하고 있습니다. 프로덕션 코드에서는 오류를 적절하게 처리해야합니다.


답변

일부 프로젝트에서는 신속한 반사를 사용합니다. 그러나 중첩 된 코드화 가능한 객체는 거기에서도 매핑되지 않습니다.

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