61

Given an array of [Any] that has a mix of optional and non optional values, e.g:

let int:Int? = 1
let str:String? = "foo"

let values:[Any] = [int,2,str,"bar"]

How can we extract the value of the Optional in the Any type (if there is one) so we can create a generic print function that only prints out the values.

E.g. this printArray function goes through and prints each element:

func printArray(values:[Any]) {
    for i in 0..<values.count {
        println("value[\(i)] = \(values[i])")
    }
}

printArray(values)

Which will output:

value[0] = Optional(1)
value[1] = 2
value[2] = Optional("foo")
value[3] = bar

How can we change it so it only prints the underlying value so that it unwraps the value if it's Optional? e.g:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar

Update Progress...

It can work when changing the argument to [Any?], e.g:

let values:[Any?] = [int,2,str,"bar"]

func printArray(values:[Any?]) {
    for i in 0..<values.count {
        println("value[\(i)] = \(values[i]!)")
    }
}

printArray(values)

Which will print the desired:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar

But would still like to see how we can unwrap an Optional from Any as this is what MirrorType.value returns making it difficult to extract the Optional value, e.g:

class Person {
    var id:Int = 1
    var name:String?
}

var person = Person()
person.name = "foo"

var mt:MirrorType = reflect(person)
for i in 0 ..< mt.count {
    let (name, pt) = mt[i]
    println("\(name) = \(pt.value)")
}

Prints out:

id = 1
name = Optional("foo")

When I need:

id = 1
name = foo
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
mythz
  • 141,670
  • 29
  • 246
  • 390
  • Really just a special case of the problem that you cannot ask whether something is an Optional... – matt Jan 16 '15 at 17:16
  • @matt yep and to extract its value, just wanted to phrase the question so answers would show this and not examples of static optional unwrapping. – mythz Jan 16 '15 at 17:18
  • 1
    Yep, upvoted for nicely phrased puzzle. Of course you won't get any answer. :) – matt Jan 16 '15 at 17:18
  • Add ! after values[i] – jwlaughton Jan 16 '15 at 17:22
  • @jwlaughton you can only do that on `Optional` types, not `Any` – mythz Jan 16 '15 at 17:24
  • @JacobBudin I want all values, but the underlying value if it's Optional – mythz Jan 16 '15 at 17:29
  • I have a way to find if something is an Optional, but then: how to convert an Any into an Optional? – qwerty_so Jan 16 '15 at 17:30
  • @ThomasKilian Cool, how can we do that? – mythz Jan 16 '15 at 17:32
  • 1
    I posted the code so far. Maybe someone takes the last hurdle. – qwerty_so Jan 16 '15 at 17:41
  • This doesn't seem to work in all cases. If optional value is inside of Dictionary, reflect() returns disposition=Aggregate instead of Optional and count is 0. :( Yet the valueType shows the correct type. – Zmey Jul 23 '15 at 15:08
  • This is an important question, but the question itself is very cluttered with examples and progress taken from the answers. It would be great if the question could be cleaned up to its base case and the progress made into an answer. – Maic López Sáenz Sep 25 '15 at 11:12
  • If you've got different solutions than the one you have accepted, please post them as an *answer*, not inside your question. Thanks. – Eric Aya Dec 13 '16 at 12:24

10 Answers10

40

For Xcode 7 and Swift 2:

func unwrap(any:Any) -> Any {

    let mi = Mirror(reflecting: any)
    if mi.displayStyle != .Optional {
        return any
    }

    if mi.children.count == 0 { return NSNull() }
    let (_, some) = mi.children.first!
    return some

}


let int:Int? = 1
let str:String? = "foo"
let null:Any? = nil
let values:[Any] = [unwrap(int),2,unwrap(str),"bar", unwrap(null)]

This will give you [1, 2, "foo", "bar", {NSObject}]

Change NSNull() to nil and the return value of unwrap func to Any? will always unwrap any type.

bubuxu
  • 2,187
  • 18
  • 23
25

To maybe save somebody from cobbling it all together from the answers and comments, here is an answer including both "sane" ways and some what I consider to be improvements for Swift 3 coming with Xcode 8.2.1.

Using Reflection

func unwrap<T>(_ any: T) -> Any
{
    let mirror = Mirror(reflecting: any)
    guard mirror.displayStyle == .optional, let first = mirror.children.first else {
        return any
    }
    return first.value
}

Discussion

The accepted answer from bubuxu fails to compile with Swift 3. As walkline suggests in his comment, changing .Optional to .optional fixes this (see SE-0005 and Swift API Design Guidelines).

Reasons I thought this solution can be improved:

  • I find returning NSNull() weird.
  • I think the alternative of returning nil with return type Any? is also problematic because it turns everything (including non-optional values) into optional values (e.g. unwrap(any: 42) returns Optional(42)).
  • When calling unwrap(any:) with anything but an Any value (any more any anybody?) the Swift 3 compiler warns about implicitly coercing to Any.

Similiar thoughts apply to Sajjon's answer.

The solution I suggest addresses all those points. Be aware however that unwrap(_:) returns nil as type Any so using the nil coalescing operator does not work anymore. This means that this just shifts around what I think is problematic about the second point. But I found this to be just the right thing to do for the (to me) more interesting use case regarding reflection.

Using an Extension on Optional

protocol OptionalProtocol {
    func isSome() -> Bool
    func unwrap() -> Any
}

extension Optional : OptionalProtocol {
    func isSome() -> Bool {
        switch self {
        case .none: return false
        case .some: return true
        }
    }

    func unwrap() -> Any {
        switch self {
        case .none: preconditionFailure("trying to unwrap nil")
        case .some(let unwrapped): return unwrapped
        }
    }
}

func unwrapUsingProtocol<T>(_ any: T) -> Any
{
    guard let optional = any as? OptionalProtocol, optional.isSome() else {
        return any
    }
    return optional.unwrap()
}

Discussion

This is bascially LopSae's solution updated to Swift 3. I also changed the precondition failure message and added unwrapUsingProtocol(_:).

Usage

class Person {
    var id:Int = 1
    var name:String?
}

var person = Person()
person.name = "foo"

let mirror = Mirror(reflecting: person)
for child in mirror.children.filter({ $0.label != nil }) {
    print("\(child.label!) = \(unwrap(child.value))")
}

No matter if you're using unwrap() or unwrapUsingProtocol(), this will print

id = 1
name = foo

If you're looking for a way to neatly align the output, see Is there a way to use tabs to evenly space out description strings in Swift?

Community
  • 1
  • 1
thm
  • 1,217
  • 10
  • 12
14

To check if a Any variable is an optional a protocol can be used as a means of a typeless Optional.

Just as its currently imposible (as of Swift 2) to check against a typeless Optional it is also not posible to cast an into a typeless optional:

let anyType: Any.Type = Optional<String>.self
let anyThing: Any = Optional.Some("string")

anyType is Optional.Type // Causes error
let maybeString = anything as? Optional // Also causes error
// Argument for generic parameter 'Wrapped' could not be inferred

However, the proposed OptionalProtocol can also be used to provide a generic-less interface to access the Optional values and even unwrap them:

protocol OptionalProtocol {
    func isSome() -> Bool
    func unwrap() -> Any
}

extension Optional : OptionalProtocol {
    func isSome() -> Bool {
        switch self {
            case .None: return false
            case .Some: return true
        }
    }

    func unwrap() -> Any {
        switch self {
            // If a nil is unwrapped it will crash!
            case .None: preconditionFailure("nill unwrap")
            case .Some(let unwrapped): return unwrapped
        }
    }
}

// With this we can check if we have an optional
let maybeString: String? = "maybe"
let justString: String = "just"

maybeString is OptionalProtocol // true
justString is OptionalProtocol  // false

With the methods provided the optionals can be checked and accessed in quite a natural way, without needing the impossible cast to Optional:

let values:[Any] = [
    Optional.Some(12),
    2,
    Optional<String>.None, // a "wrapped" nil for completeness
    Optional.Some("maybe"),
    "something"
]

for any in values {
    if let optional = any as? OptionalProtocol {
        if optional.isSome() {
            print(optional.unwrap())
        } else {
            // nil should not be unwrapped!
            print(optional)
        }
        continue
    }

    print(any)
}

Which will print:

12
2
nil
maybe
something
Community
  • 1
  • 1
Maic López Sáenz
  • 10,385
  • 4
  • 44
  • 57
8

Slight alteration on @thm to completely unwrap:

func unwrap<T>(_ any: T) -> Any {
    let mirror = Mirror(reflecting: any)
    guard mirror.displayStyle == .optional, let first = mirror.children.first else {
        return any
    }
    return unwrap(first.value)
}
Chris Prince
  • 7,288
  • 2
  • 48
  • 66
7

I think this is a kind of bug.

In general, to discover and extract the specific type from Any, down casting with as is the only supported method. But :

let int:Int? = 1
let any:Any = int

switch any {
case let val as Optional<Int>: // < [!] cannot downcast from 'Any' to a more optional type 'Optional<Int>'
    print(val)
default:
    break
}

This means, there is no supported way to do that.

Anyway, apparently you can do that with reflect:

func printArray(values:[Any]) {
    for i in 0..<values.count {
        var val = values[i]

        var ref = reflect(val)
        // while `val` is Optional and has `Some` value
        while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
            // replace `val` with unwrapped value
            val = ref[0].1.value;
            ref = reflect(val)
        }

        println("value[\(i)] = \(val)")
    }
}

let int:Int? = 1
let str:String? = "foo"

let values:[Any] = [int,2,str,"bar"]

printArray(values)

outputs:

value[0] = 1
value[1] = 2
value[2] = foo
value[3] = bar

ADDED: minor tweaked version

func printArray(values:[Any]) {
    for i in 0..<values.count {

        var ref = reflect(values[i])
        // while `val` is Optional and has `Some` value
        while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
            // Drill down to the Mirror of unwrapped value
            ref = ref[0].1
        }
        let val = ref.value

        println("value[\(i)] = \(val)")
    }
}

Factoring out into a function:

func unwrapAny(val:Any) -> Any {
    var ref = reflect(val)
    while ref.disposition == .Optional && ref.count > 0 && ref[0].0 == "Some" {
        ref = ref[0].1
    }
    return ref.value
}

func printArray(values:[Any]) {
    for i in 0..<values.count {
        println("value[\(i)] = \(unwrapAny(values[i]))")
    }
}
rintaro
  • 51,423
  • 14
  • 131
  • 139
1

Not a complete answer. It boils down to this:

let int:Int? = 1
let str:String? = "foo"

let values:[Any] = [int,2,str,"bar"]
func printArray(values:[Any]) {
  for i in 0..<values.count {
    let v = values[i]
    if _stdlib_demangleName(_stdlib_getTypeName(v)) == "Swift.Optional" {
      println("value[\(i)] = "it's optional: \(v)") // here I'm stuck
    }else {
      println("value[\(i)] = \(values[i])")
    }
  }
}

printArray(values)
qwerty_so
  • 35,448
  • 8
  • 62
  • 86
  • FYI can look a little nicer with: `if "\(_stdlib_demangleName(_stdlib_getTypeName(v)))" == "Swift.Optional" {...}` – mythz Jan 16 '15 at 17:50
  • Yes, of course I thought of this. But although you can use the stdlib methods to cheat and see, as a string, that something is an optional, you can't cast so you can't unwrap. Plus I wouldn't count on the undocumented stdlib methods to stick around. The puzzle is best left as a puzzle: there's a hole in the language. – matt Jan 16 '15 at 18:18
  • We can also change it to avoid internal stdlib methods with: `if reflect(values[i]).disposition == MirrorDisposition.Optional { ... }`. @matt happy to get something working now (if possible?) and change it later once a formal API is available. – mythz Jan 16 '15 at 18:41
  • @mythz so it's not just a chess problem? You actually have a use case? That surprises me. – matt Jan 16 '15 at 18:51
  • @matt Yeah looking at creating a generic JSON serialization library, need to extract the Optional value for serialization. – mythz Jan 16 '15 at 19:00
  • @mythz but then I don't understand why you don't just start out with an `[Any?]`. Problem solved; now everything is an Optional and everything is unwrappable. This solution was obvious to me right from the start; that is why I assumed this question was just a kind of joke, a way of yanking Swift's chain. (And in fact I see that someone else has now given this as an answer.) – matt Jan 16 '15 at 19:26
  • @matt Yeah I'm continuing to see how far I can get with `Any?` but leaving this question open as I still want to know if it's possible. It might be related to [a restriction in Swift that Apple's planning to remove in a future release](https://devforums.apple.com/message/1072650#1072650). – mythz Jan 16 '15 at 19:31
  • Fascinating but I'm not sure I think this is related. – matt Jan 16 '15 at 19:37
  • @mythz: looks a bit nicer indeed :-) – qwerty_so Jan 16 '15 at 19:59
  • @matt Unfortunately `MirrorType.value` returns `Any` so I'll still need to extract the Optional value from `Any` type. – mythz Jan 16 '15 at 21:55
1

how about this solution, I made a generic version of previous answer.

fileprivate func unwrap<T>(value: Any)
  -> (unwraped:T?, isOriginalType:Bool) {

  let mirror = Mirror(reflecting: value)
  let isOrgType = mirror.subjectType == Optional<T>.self
  if mirror.displayStyle != .optional {
    return (value as? T, isOrgType)
  }
  guard let firstChild = mirror.children.first else {
    return (nil, isOrgType)
  }
  return (firstChild.value as? T, isOrgType)
}

let value: [Int]? = [0]
let value2: [Int]? = nil

let anyValue: Any = value
let anyValue2: Any = value2

let unwrappedResult:([Int]?, Bool)
  = unwrap(value: anyValue)    // ({[0]}, .1 true)
let unwrappedResult2:([Int]?, Bool)
  = unwrap(value: anyValue2)  // (nil, .1 true)
let unwrappedResult3:([UInt]?, Bool)
  = unwrap(value: anyValue)  // (nil, .1 false)
let unwrappedResult4:([NSNumber]?, Bool)
  = unwrap(value: anyValue)  ({[0]}, .1 false)

The following is code on Playground.

enter image description here

Keith
  • 111
  • 1
  • 6
0

Based on the solution by @bubuxu, one can also:

func unwrap<T: Any>(any: T) -> T? {
    let mirror = Mirror(reflecting: any)
    guard mirror.displayStyle == .optional else { return any }
    guard let child = mirror.children.first else { return nil }
    return unwrap(any: child.value) as? T
}

But you need to check against nil using ?? nil when using unwrap, as done in foo

func foo<T>(_ maybeValue: T?) {
    if let value: T = unwrap(any: maybeValue) ?? nil {
        print(value)
    }
}

Still neat though!

(Anyone got a solution for the ?? nil check?)

Sajjon
  • 8,938
  • 5
  • 60
  • 94
-1

Without making it too complicated, why not:

let int:Int? = 1
let str:String? = "foo"

let values:[Any?] = [int,2,str,"bar"]

for var i:Int = 0; i < values.count; i++
{
    println("\(values[i]!)")
}

This prints:

1
2
foo
bar

jwlaughton
  • 905
  • 1
  • 6
  • 11
  • This is only okay if you know there will be a value, which with optionals is not usually the case. – Andrew Robinson Jan 17 '15 at 18:58
  • This is interesting. The use of `Any?` is actually unwrapping any optional and re-wrapping it as Any?. That means if you do `let any: Any = Optional.Some("maybe")` then `any.dynamicType` will be `Optional`. But if you do `let maybeAny: Any? = Optional.Some("maybe")` then `maybeAny.dynamicType` will be `Optional>`. – Maic López Sáenz Sep 25 '15 at 21:06
-2

According to Using Enumeration case patterns in Swift 2.0 those might be look like this:

let pattern :[Int?]  = [nil, 332, 232,nil,55]
for case let number? in pattern {
   print(number)
}

Output: 332, 232, 55

Narendra G
  • 491
  • 4
  • 9