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


답변:


231

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

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를 살펴보십시오.


22

여기서 간단하게 구현되어 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


고마워요! 대안은 상속을 사용하는 것이지만 호출 사이트는 다른 반환 유형의 두 가지 함수가 있기 때문에 유형을 사전으로 추론 할 수 없습니다.
user1046037

17

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

import CodableFirebase

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

7

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

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
이는 같은 종류의 모든 속성과 구조를 위해 일할 것
레오 버스들이시길에게

1
방금 "let dict = try JSONDecoder (). decode ([String : Int] .self, from : JSONEncoder (). encode (foo))"를 시도했고 "Dictionary <String, Any>를 디코딩 할 것으로 예상했지만 대신 배열합니다. " u는 pls를 도울 수
있습니까

6

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


6

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

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


4

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

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

2

사용하는 것만으로도 가치가 있다고 생각합니다 CodableJSON / Plists / 무엇이든 치는 의도없이 사전으로 /부터 인코딩하는 데 . 사전을 돌려 주거나 사전을 기대하는 많은 API가 있으며 끝없는 상용구 코드를 작성하지 않고도 Swift 구조체 또는 객체와 쉽게 교환 할 수 있다는 점이 좋습니다.

Foundation JSONEncoder.swift 소스 (실제로 사전 인코딩 / 디코딩을 내부적으로 구현하지만 내 보내지 않음)를 기반으로하는 일부 코드를 가지고 놀았습니다.

코드는 여기에서 찾을 수 있습니다 : https://github.com/elegantchaos/DictionaryCoding

여전히 상당히 거칠지 만 디코딩 할 때 누락 된 값을 기본값으로 채울 수 있도록 약간 확장했습니다.


2

Swift 프로젝트 의 PropertyListEncoder 를 DictionaryEncoder로 수정했습니다 . 사전에서 이진 형식으로 최종 직렬화를 제거하기 만하면됩니다. 직접 똑같이 할 수도 있고 여기 에서 내 코드를 가져올 수도 있습니다.

다음과 같이 사용할 수 있습니다.

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

0

나는 이것을 처리하기 위해 빠른 요점 을 썼다 (Codable 프로토콜을 사용하지 않음). 주의하십시오. 어떤 값도 유형 검사하지 않으며 인코딩 가능한 값에 대해 재귀 적으로 작동하지 않습니다.

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

Codable에서는이를 수행하는 직접적인 방법이 없습니다. 구조체에 대해 Encodable / Decodable 프로토콜을 구현해야합니다. 예를 들어 다음과 같이 작성해야 할 수도 있습니다.

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

내가 여기에 포드를 만든 https://github.com/levantAJ/AnyCodable 촉진하기 위해 디코딩인코딩을 [String: Any] 하고[Any]

pod 'DynamicCodable', '1.0'

그리고 디코딩 및 인코딩 [String: Any][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)
    }
}

1
귀하의 예는 문제 해결 방법을 보여주지 않습니다.
사이먼 Moshenko

0

SwiftyJSON을 사용하는 경우 다음과 같이 할 수 있습니다.

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

노트 : Alamofire 요청 parameters대해이 사전을 전달할 수도 있습니다 .


0

다음은 프로토콜 기반 솔루션입니다.

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

그리고 그것을 사용하는 방법은 다음과 같습니다.

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

여기에 사전-> 객체가 있습니다. 스위프트 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

생각해 보면 Encodable인스턴스가 배열과 같이 딕셔너리로 ​​직렬화 할 수없는 것이기 때문에 일반적인 경우에는 질문에 답이 없습니다 .

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

그 외에는 프레임 워크와 비슷한 것을 작성했습니다 .


나는 이것이 왜 반대표를 던 졌는지 아직도 이해가 안된다는 것을 인정해야합니다. :–)주의 사항이 사실이 아닙니까? 아니면 프레임 워크가 유용하지 않습니까?
zoul
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.