问题
I'm looking to store models objects in a Dictionary and would like to serialize the whole dictionary using JSONEncoder
into data and subsequently into a string and save it.
The idea is to use Swift 4's out of the box Encodable
to ensure anything that I add to the dictionary will be serialized which can include primitives and custom objects (which will themselves conform to Encodable
).
The Challenge is what type should I declare the dictionary to be:
- If I use
[String: Any]
, it won't know how to encodeAny
, and if I have to cast it into an actual concrete type, it kind of defeats the purpose of generics - If I use
[String: Encodable]
, it will crash at run time saying Encodable doesn't conform to itself, which is understandable as it needs a concrete type
In order to tackle this, I thought of creating a wrapper: i.e A protocol with an associated type or a struct with generic type value:
struct Serializable<T: Encodable> {
var value: T?
init(value: T) {
self.value = value
}
}
But the problem remains, while declaring the type of the aforementioned dictionary, I still have to supply the concrete type..
var dictionary: [String: Serializable<X>]
What should 'X' be here, Or, what's the correct way to achieve this? What am I missing?
回答1:
Two possible approaches:
You can create dictionary whose values are
Encodable
wrapper type that simply encodes the underlying value:struct EncodableValue: Encodable { let value: Encodable func encode(to encoder: Encoder) throws { try value.encode(to: encoder) } }
Then you can do:
let dictionary = [ "foo": EncodableValue(value: Foo(string: "Hello, world!")), "bar": EncodableValue(value: Bar(value: 42)), "baz": EncodableValue(value: "qux") ] let data = try! JSONEncoder().encode(dictionary)
You can define your own
Codable
type instead of using dictionary:struct RequestObject: Encodable { let foo: Foo let bar: Bar let baz: String } let requestObject = RequestObject( foo: Foo(string: "Hello, world!"), bar: Bar(value: 42), baz: "qux" ) let data = try! JSONEncoder().encode(requestObject)
Needless to say, these both assume that both Foo
and Bar
conform to Encodable
.
回答2:
This is my solution (improved by Rob answer):
struct EncodableValue: Encodable {
let value: Encodable
func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
struct Storage: Encodable {
var dict: [String: Encodable] = [:]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dict {
guard let codingKey = CodingKeys(stringValue: key) else {
continue
}
if let enc = value as? EncodableValue {
try container.encode(enc, forKey: codingKey)
}
}
}
struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
}
let dict: [String: EncodableValue] = ["test": EncodableValue(value:1), "abc":EncodableValue(value:"GOGO")]
let storage = Storage(dict: dict)
do {
let data = try JSONEncoder().encode(storage)
let res = String(data: data, encoding: .utf8)
print(res ?? "nil")
} catch {
print(error)
}
来源:https://stackoverflow.com/questions/48358371/store-encodables-in-a-swift-dictionary