2

Using the PartialKeyPath API, how can you access a value of a key path's reference? For example, this works for non-optional values, but not with Optional values.

The issue I'm having is that self[keyPath: keyPath] returns a non-optional Any value.

struct Element {

    let name: String
    let mass: Double?

    func stringValue(_ keyPath: PartialKeyPath<Element>) -> String {
         let value = self[keyPath: keyPath]

         switch value {
         case let string as String:
             return string.capitalized
         case nil:
             return "N/A"
         case let value:
             return String(describing: value)
         }
    }
}

let element = Element(name: "Helium", mass: 4.002602)
let string = element.stringValue(\Element.mass) /* Optional(4.002602) */

The result is that case nil is never executed and the last case is being printed as Optional(value).

How can I unwrap value properly to extract the optional?

jimj
  • 1,005
  • 2
  • 11
  • 22
  • 1
    Possible helpful: [How to unwrap an optional value from Any type?](https://stackoverflow.com/questions/27989094/how-to-unwrap-an-optional-value-from-any-type). – Martin R Jul 01 '18 at 20:17
  • 1
    Also possibly helpful: https://forums.swift.org/t/get-value-after-assigning-string-any-to-string-any/10595/3?u=hamishknight (see the `optionalPromotingCast(_:)` function). – Hamish Jul 01 '18 at 21:13

1 Answers1

2

The solution was to use Mirror to unwrap the optional which seems less than optimal. Looking forward to better Reflection support in Swift!

func unwrap(_ value: Any) -> Any? {
    let mirror = Mirror(reflecting: value)

    if mirror.displayStyle != .optional {
        return value
    }

    if let child = mirror.children.first {
        return child.value
    } else {
        return nil
    }
}

struct Element {

    let name: String
    let mass: Double?

    func stringValue(_ keyPath: PartialKeyPath<AtomicElement>) -> String {
        guard let value = unwrap(self[keyPath: keyPath]) else {
            return "N/A"
        }

        switch value {
        case let string as String:
            return string.capitalized
        default:
            return String(describing: value)
        }
    }
}

let element = Element(name: "Helium", mass: 4.002602)
let string = element.stringValue(\Element.mass) /* 4.002602 */
jimj
  • 1,005
  • 2
  • 11
  • 22