Serializing generic structs

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.

Answered by QuinceyMorris in 18725022

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.

The dict is a great way to clean up a 2 page switch statement.


It's a bit of a double edged sword. It might be harder to debug if two types have a collision. Perhaps put some runtime asserts to ensure there aren't any.


The question, I suppose, becomes: When to register?


You will have to ensure that each class has an instance created before any deserilization happens in order to invoke a dispatch_once in the init() for registration.


Either that or put something in at app startup that knows about all the types and calls the register on each class.


This is where objc `-(void)initialize` equivalent would be nice.


Here's the main problem: How to get the value type out of a SerializableStruct? It doesn't have a value instance, as that's in the generic.

Serializing generic structs
 
 
Q