Charlie Monroe
Charlie Monroe

Reputation: 1250

Swift - matching class dynamically

I am trying to write an extension method on Dictionary with the following signature:

func firstNonNilObjectForKey(keys: [Key], ofClass aClass: Any.Type = String.self) -> Value?

This is meant to help me deal with JSON dictionaries, where I often need the first non-null value, but sometimes the JSON includes null itself as value, which is converted to NSNull in Cocoa.

The usage would be something like:

let dict: [String:AnyObject] = [...]
let myValue = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String 

The issue is implementational - how to match the class:

if let value = self[key] {
    if value is aClass { ... } <--- error: aClass is not a type
    if let castedValue = value as? aClass { ... } <--- error: ditto

    let type = value.dynamicType
    if type == aClass { ... } <--- no error, but doesn't handle subclasses!

    // Cannot use value.isKindOfClass() since value may be Any
}

I have thought of an alternative:

func firstNonNilObjectForKey<ReturnValueType>(keys: [Key]) -> ReturnValueType?

which allows to be implemented as

if let value = self[key] as? ReturnValueType { ... }

But: - Here I cannot set the default type (I mostly need String.Type). - I'm not really sure how to invoke this:

let value = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String <--- error: Cannot invoke 'firstNonNilObjectForKey' with an argument list of type '([String])'
let value = dict.firstNonNilObjectForKey<String>([ ... ]) <--- error: Cannot explicitly specialize a generic function

I hope this isn't a duplicate, but most similar questions here are simply handling a situation where you are matching against a known class.

Upvotes: 1

Views: 62

Answers (1)

Gabriele Petronella
Gabriele Petronella

Reputation: 108111

I'm not sure I got the requirements exactly, but can something like this work for you?

extension Dictionary {

  func firstNonNilObjectForKey(keys: [Key]) -> String? {
    return self.firstNonNilObjectForKey(keys, ofClass: String.self)
  }

  func firstNonNilObjectForKey<T>(keys: [Key], ofClass aClass: T.Type) -> T? {
    for k in keys {
      if let v = self[k] as? T {
        return v
      }
    }
    return nil
  }

}

let dict = ["key1": 2, "key2": "Foo"]
let aString = dict.firstNonNilObjectForKey(["key1", "key2"]) // "Foo"
let anInt = dict.firstNonNilObjectForKey(["key1", "key2"], ofClass: Int.self) // 2

The main gotcha here is that I'm using overloading as opposed to default parameters, which don't seem to get along well with the swift type checker.

Upvotes: 3

Related Questions