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
, JSONDecoder
및 JSONSerialization
도 전략을 디코딩 / 인코딩 처리 즉, ...
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.
'Programing' 카테고리의 다른 글
rails는 yield : area가 content_for에 정의되어 있는지 확인합니다. (0) | 2020.09.02 |
---|---|
디렉터리 구조를 복사하지만 특정 파일 만 포함하는 방법 (Windows 배치 파일 사용) (0) | 2020.09.02 |
리플렉션을 사용하여 C #에서 기본 생성자없이 유형의 인스턴스 만들기 (0) | 2020.09.02 |
테이블 수준 백업 (0) | 2020.09.02 |
Eclipse-디버거가 중단 점에서 중지되지 않음 (0) | 2020.09.02 |