What is the best approach to archive/serialize generic structs?
I cannot use NSCoding (since structs are not NSObjects).
I can translate my structs to some other representation (such as JSON strings),
but I'm struggling to unarchive/deserialize the structs back from the JSON
if the structs are generic.
More precisely, I'm wrestling with the type system to allow me to express
this in an elegant, maintainable way.
Well, it's a bit voluminous, and getting the generic creation function to compile was a bit strange, but this is what I ended up with:
protocol SerializableType {
static var serializableName: String { get }
var serializableProperties: [String: Any] { get }
init (serializedProperties: [String: Any])
}
extension Int: SerializableType {
static var serializableName: String { return "Int" }
var serializableProperties: [String: Any] {
return ["value": self]
}
init (serializedProperties: [String: Any]) {
self = serializedProperties ["value"] as! Int
}
}
extension String: SerializableType {
static var serializableName: String { return "String" }
var serializableProperties: [String: Any] {
return ["value": self]
}
init (serializedProperties: [String: Any]) {
self = serializedProperties ["value"] as! String
}
}
struct A: SerializableType {
static var serializableName: String { return "A" }
var serializableProperties: [String: Any] {
return ["a1": a1, "a2": a2]
}
init (a1: Int, a2: String) {
self.a1 = a1
self.a2 = a2
}
init (serializedProperties: [String: Any]) {
a1 = serializedProperties ["a1"] as! Int
a2 = serializedProperties ["a2"] as! String
}
var a1: Int
var a2: String
}
struct B<T: SerializableType>: SerializableType {
static var serializableName: String { return "B<\(T.serializableName)>" }
var serializableProperties: [String: Any] {
return ["b1": b1, "b2": b2, "b3": b3 as Any]
}
init (b1: Int, b2: String, b3: T) {
self.b1 = b1
self.b2 = b2
self.b3 = b3
}
init (serializedProperties: [String: Any]) {
b1 = serializedProperties ["b1"] as! Int
b2 = serializedProperties ["b2"] as! String
b3 = serializedProperties ["b3"] as! T
}
var b1: Int
var b2: String
var b3: T
}
func serialize (instance: SerializableType) -> [String: Any] {
return ["type": instance.dynamicType.serializableName, "values": instance.serializableProperties]
}
func deserialize (serialization: [String: Any]) -> SerializableType {
let type = serialization ["type"] as! String
let typeName: String = …
let typeParameter: String? = …
let values = serialization ["values"] as! [String: Any]
if let typeParameter = typeParameter {
switch typeParameter {
case "A":
return deserializedValueWithParameter (A.self, typeName: typeName, values: values)
case "Int":
return deserializedValueWithParameter (Int.self, typeName: typeName, values: values)
case "String":
return deserializedValueWithParameter (String.self, typeName: typeName, values: values)
default:
fatalError ()
}
}
else {
switch typeName {
case "A":
return A (serializedProperties: values)
case "Int":
return Int (serializedProperties: values)
case "String":
return String (serializedProperties: values)
}
}
}
func deserializedValueWithParameter<T: SerializableType> (parameterType: T.Type, typeName: String, values: [String: Any]) -> SerializableType {
switch typeName {
case "B":
return B<T> (serializedProperties: values)
default:
fatalError ()
}
}
let b = B (b1: 0, b2: "X", b3: 1)
let bb = serialize (b)
let bbb = deserialize (bb)
Note:
— The code for serializing and deserializing actually needs to be recursive, but for reasons of space I didn't write that code here.
— I've already written all of the non-generic implemention of this as real code in a project I'm working on. In the real code, known types like Int and String are special cased, rather than handled like the above.
— I didn't write the code to analyze the "B<A>" string into its components, so the last 'bbb = …' line crashes, but the rest of it works in a playground.
— I didn't write any error handling, I just threw in "as!" everywhere.
— I didn't really serialize anything, just converted to a plist-style dictionary that can be serialized easily into JSON, or a plist, or a NSKeyedArchiver archive, according to preference.
— But the number of cases for generics is additive, not multiplicative, so this approach looks a lot better if the number of serializable types is bigger.
— Sorry, the indentation got messed up when I pasted.