vendredi 12 février 2021

Dynamically access property of property of struct

I have a large numbers of structs and all of them have responseId: String and there is a property with same name as the value of responseId that contains contactId: String.

  • responseId is always non-optional String.
  • contactId of inner object is also non-optional String
  • all inner objects are optional (at runtime only one of the objects will be non-nil)

Below are two examples:

protocol ContainerBase {
    var responseId: String { get }
}

struct Container1: ContainerBase {
    struct OtherA {
        let contactId: String
        let cats: [String] // Other info here, not related to OtherB
    }
    struct OtherB {
        let contactId: String
        let dogsNum: Int // Other info here, not related to OtherA
    }
    let responseId: String
    let otherA: OtherA? // Optional
    let otherB: OtherB? // Optional
}

struct Container2: ContainerBase {
    struct AnotherA {
        let contactId: String
        let passed: Bool  // Other info here, not related to AnotherB
    }
    struct AnotherB {
        let contactId: String
        let friend: String // Other info here, not related to AnotherA
    }
    let responseId: String
    let anotherA: AnotherA? // Optional
    let anotherB: AnotherB? // Optional
}

Question:

How can I access contactId from Container1 or Container2 dynamically? (I tried the non dynamic approach with an extra function for each ContainerN struct with a switch inside but this is getting crazy because I have too many this structs, I already made some typos and forgot some cases, caused bugs,... and I imagine the reliable solution is "reflexion"?).

Example:

For example, if responseId of Container1 is "otherA" then I should look for contactId inside of property otherA. Since I have several types of Containers with different types of unrelated inner objects each one, solution should not be specific to Container1 nor Container2 it should work with any ContainerBase.

I implemented a dirty code but it causes a warning and cannot find to work it without generating one. Also I think this does not work reliably (This is for my iOS app but this strangely this does not work in linux Swift). Is this even possible? or it is a compiler glitch?

let c1 = Container1(
    responseId: "otherA",
    otherA: Container1.OtherA(contactId: "123", cats: ["figaro"]),
    otherB: nil)

c1.findContactId() // expected "123"

Ugly code ahead:

extension ContainerBase {
    func findContactId() -> String? {
        let mirror = Mirror(reflecting: self)
        guard let tInnerRes = mirror.children.first(where: { $0.label == self.responseId })?.value else { return nil }

        // WARN: Conditional cast from 'Any' to 'Optional<Any>' always succeeds
        guard let maybeInnerRes = (tInnerRes as? Optional<Any>) else { return nil }

        guard let innerRes = maybeInnerRes else { return nil }
        let innerMirror = Mirror(reflecting: innerRes)
        let contactId = innerMirror.children.first(where: { $0.label == "contactId" })?.value as? String
        return contactId
    }
}

Any help is appreciated





Aucun commentaire:

Enregistrer un commentaire