Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 2.0 Get Mirrored Superclass Properties

I need to get the properties of my class as a dictionary. For simplicity, I created a protocol which has a default implementation as follows:

protocol ListsProperties{
    func toDictionary() -> [String: AnyObject]
}

extension ListsProperties{
    func toDictionary() -> [String: AnyObject] {
        let mirrored_object = Mirror(reflecting: self)
        var dict = [String: AnyObject]()
        for (_, attr) in mirrored_object.children.enumerate() {
            if let propertyName = attr.label as String! {
                dict[propertyName] = attr.value as? AnyObject
            }
        }     

        return dict
    }
}

My classes can conform to this protocol and will have the toDictionary() method available. However, this does not work if I use the method on a subclass, as it will produce only the properties defined on the subclass and ignore the parent superclass properties.

Ideally I could find some way to call the toDictionary() method on the mirrored superclass as this would then call toDictionary() on its own superclass and the compiler says that the superclass mirror does not conform to the Protocol even though the class it is mirroring does.

The following works but only if there is only one superclass so isn't sufficient:

func toDictionary() -> [String: AnyObject] {
    let mirrored_object = Mirror(reflecting: self)
    var dict = [String: AnyObject]()
    for (_, attr) in mirrored_object.children.enumerate() {
        if let propertyName = attr.label as String! {
            dict[propertyName] = attr.value as? AnyObject
        }
    }

    // This is an issue as it limits to one subclass 'deep' 
    if let parent = mirrored_object.superclassMirror(){
        for (_, attr) in parent.children.enumerate() {
            if let propertyName = attr.label as String!{
                if dict[propertyName] == nil{
                    dict[propertyName] = attr.value as? AnyObject
                }
            }
        }
    }

    return dict
}

Any ideas on how I could modify the default implementation of toDictionary() to include superclass attributes (and the attributes of any superclasses of the superclass etc)?

like image 470
ExoticChimp Avatar asked Nov 24 '15 17:11

ExoticChimp


3 Answers

One possible solution would be to implement toDictionary() as a method of Mirror itself, so that you can traverse recursively to the superclass mirror:

extension Mirror {

    func toDictionary() -> [String: AnyObject] {
        var dict = [String: AnyObject]()

        // Properties of this instance:
        for attr in self.children {
            if let propertyName = attr.label {
                dict[propertyName] = attr.value as? AnyObject
            }
        } 

        // Add properties of superclass:
        if let parent = self.superclassMirror() {
            for (propertyName, value) in parent.toDictionary() {
                dict[propertyName] = value
            }
        }

        return dict
    }
}

and then use that to implement the protocol extension method:

extension ListsProperties {
    func toDictionary() -> [String: AnyObject] {
        return Mirror(reflecting: self).toDictionary()
    }
}
like image 152
Martin R Avatar answered Oct 13 '22 02:10

Martin R


You can simply loop through all the superclassMirrors get the properties. It doesn't matter how many layers there are.

var mirror: Mirror? = Mirror(reflecting: child) repeat { for property in mirror!.children { print("property: \(property)") } mirror = mirror?.superclassMirror() } while mirror != nil

like image 35
superarts.org Avatar answered Oct 13 '22 03:10

superarts.org


By using a protocol, you require that all the superclasses implement such a protocol to be able to use the function.

I would use a helper class, so that you can pass any object to it.

class ListsPropertiesHelper {
    static func toDictionary(mirrored_object: Mirror) -> [String: AnyObject] {
        var dict = [String: AnyObject]()
        for (_, attr) in mirrored_object.children.enumerate() {
            if let propertyName = attr.label as String! {
                dict[propertyName] = attr.value as? AnyObject
            }
        }
        if let parent = mirrored_object.superclassMirror() {
            let dict2 = toDictionary(parent)
            for (key,value) in dict2 {
                dict.updateValue(value, forKey:key)
            }
        }

        return dict
    }

    static func toDictionary(obj: AnyObject) -> [String: AnyObject] {
        let mirrored_object = Mirror(reflecting: obj)
        return self.toDictionary(mirrored_object)
    }
}

Then you can use it like

let props = ListsPropertiesHelper.toDictionary(someObject)

If you still want to be able to write

let props = someObject.toDictionary()

you can implement the protocol with the extension calling the helper class.

like image 29
Marcos Crispino Avatar answered Oct 13 '22 03:10

Marcos Crispino