Swift Generic Object/JSON Serialization

ε祈祈猫儿з 提交于 2020-01-02 03:37:08

问题


tl;dr Can you generically instantiate an object using a conforming protocol's initializer method while also still preserving the object's intended type? What I am trying now is seg faulting the compiler.


In a library I am writing, I am trying to accomplish a goal of generically serializing/deserializing objects using Swift's limited introspection features.

Here is the code for a global function that sets a variable's value using reflection. It attempts to reconcile for nested constructs if it finds a dictionary:

func model__setValue<T where T: NSObject, T: Serializable>(value: AnyObject, forSerializationKey key: String, model m: T) {
    let varNames = object__getVarNames(mirror: reflect(m)) // Gets a list of this object's variable names
    if let i = find(m.serializationKeys, key) {
        if value is [String : AnyObject] {
            // This allows us to have nested dictionary representations
            // of Serializable constructs and have them init properly
            let t1 = reflect(m)[i].1.valueType as NSObject.Type
            if t1 is Serializable.Type {
                let t2 = t1 as Serializable.Type
                let finalObj = t2(dictionary: value as [String : AnyObject]) // Segmentation fault: 11
                m.setValue(finalObj, forKey: varNames[i])
            }
        } else {
            m.setValue(value, forKey: varNames[i])
        }
    }
}

Couple of things to explain:

  1. Serializable is a protocol defining methods including init(dictionary). It is adopted by objects that want to be (de)serialized.
  2. Serializable also specifies a computed property of "serialization keys" or a list of strings that are used as the dictionary keys for the object's variables and must be a one-to-one mapping of variable names to serialization keys. Why do this? Sometimes, API calls return keys that don't really make sense anymore (schema rot?), or I just want to name a variable differently, as I don't tend to enjoy underscores too much.
  3. The model has to both be an NSObject and conform to Serializable. Why? To achieve generics like this, I needed the ability to write to an object's fields without know their identifiers ahead of time. Swift doesn't have a way to do this natively, so subclassing NSObject is a good compromise. Why not have a root object with those features anyway?

My goal with this is stupid easy, low overhead construction of objects from their JSON representation (even with different keys and variable names, even with nested objects). So give an object it's list of keys and make the protocol's init call another generic constructor function which iterates over the object's variables and calls this each time. Bam. That's all. Now you can parse all the things from all the (JSON) APIs and write extremely little or no parse logic. I wanted the same to apply to a serializer too, such that toDictionary also requires just one generic method call.

In this function, we are setting an object's value dynamically using reflection. First, we make sure this is a valid key to set on the object. Next, we determine whether or not the value we want to set is a dictionary. If it is, I want to grab the target object's type to see if it is in fact also a Serializable conforming type. If that field is, we can use this dictionary to recursively construct it (this doesn't work as explained shortly). If it isn't, or the value isn't a dictionary at all, just use Cocoa's setValue:forKey: method.

Alas, I can't get the nested object part to work quite right, and I haven't figured out if what I am trying to do is just impossible or if I am just doing it wrong. I'm also concerned my let finalObj line isn't valid, despite Xcode's lack of error on it. My thought process is just call Serializable's init constructor and go on my merry way. I assume the issue with this is Swift's statically typed nature and the compiler not actually knowing what final type the init will return (although, Xcode highlight's t2 in that line in the same color as it does keywords...). I am not really sure if there's a way to say t1 is an NSObject that also conforms to Serializable. I think what I am attempting now with t2 and the cast is losing type information and thus causing the seg fault, though again Xcode doesn't complain about it until I actually go to build.

I've tried several ways of reconciling this nested Serializable thing and haven't been able to come up with a solution. Would anyone be able to assist?


回答1:


Check out this article, somebody else did something just like what you are trying. It might help you, or it might actually be just what you are looking for: https://www.weheartswift.com/swift-objc-magic/



来源:https://stackoverflow.com/questions/27194384/swift-generic-object-json-serialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!