0

Hi I am struggle to solve the problem dynamic protocol conformance in swift language. Please see code.

Protocol:

protocol Object {

    init(by object: [String: Any])
}

Custom structs with protocol object conformance:

struct Tree: Object {

    let treeName: String

    init(by object: [String: Any]) {

        self.treeName = object["tree"] as? String ?? "Notree"
    }
}

struct Plant: Object {

    let plantName: String

    init(by object: [String : Any]) {

        self.plantName = object["tree"] as? String ?? ""
    }
}

The above code just fine until the object is [String: Any]. I can't use [[String: Any]] like below.

let coconut = ["tree":"Coconut"] // => This fine
let allTrees = [["tree":"Apple"],["tree":"Orange"],["tree":"Jakfruit"]] //=> Here is the problem

 let aTree = Tree(by: coconut)
 let bTree = Tree(by: ["data":allTrees])
 let cTree = Plant(by: ["data":allTrees])

I can't use array of objects. So, I used to store objects in to key "data". Now I used extension: Array confirm protocol object.

extension Array: Object where Element == Object{

    init(by object: [String : Any]) {

        if let data = object["data"] as? [[String: Any]]{

            self  = data.map({ (object) -> Object in

//                return Plant.init(by: object) // => Works, But I need dynamic confirmance
//                return Tree.init(by: object) // => Works, But I need dynamic confirmance

                return Object.init(by: object) //=> How can I do?
            })
        }else{

            self = []
        }
    }
}

The return Object shows error Protocol type 'Object' cannot be instantiated. I tried lot to solve but not able.

Can someone suggest better idea or solution for this problem? Thank you in advance...

0

1 Answer 1

1

First, you should not use the constraint == Object. You want to say that not only [Object] is an Object, but also [Plant] and [Tree] are Objects too, right? For that, you should use the : Object constraint. Second, you can use Element.init to initialise a new Element of the array. Because of the constraint Element : Object, we know that a init(by:) initialiser exists:

extension Array: Object where Element: Object{

    init(by object: [String : Any]) {

        if let data = object["data"] as? [[String: Any]]{

            self  = data.map({ (object) in

                return Element.init(by: object)
            })
        }else{

            self = []
        }
    }
}

Usage:

let trees = [Tree](by: ["data": allTrees])

Here's what I think a more Swifty version of your code, making use of failable initialisers - initialisers that return nil when they fail to initialise the object:

protocol Object {

    init?(by object: [String: Any])
}


struct Tree: Object {

    let treeName: String

    init?(by object: [String: Any]) {

        if let treeName = object["tree"] as? String {
            self.treeName = treeName
        } else {
            return nil
        }
    }
}

struct Plant: Object {

    let plantName: String

    init?(by object: [String : Any]) {

        if let plantName = object["tree"] as? String {
            self.plantName = plantName
        } else {
            return nil
        }
    }
}

extension Array: Object where Element: Object{

    init?(by object: [String : Any]) {

        if let data = object["data"] as? [[String: Any]]{
            self  = data.compactMap(Element.init)
        }else{
            return nil
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much! I spent about few hours to find this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.