Swift 2 dictionary extension for a property list, how to?

I'm in the midst of rewriting some Objective-C code in Swift, and I have a bit of code that extended NSDictionary through a category, for the purpose of easing access to a structured property list read from a file. I'm stuck on how to declare and implement the equivalent Swift Dictionary extension. Being from a property list, assume the dictionary generally conforms to Dictionary<String, AnyObject>, and the leaf elements are String, Number (integerValue), Boolean (boolValue), or Array (Array<AnyObject>, array elements are String, Number or Boolean). Here are some example functions in the Objective-C category:


@implementation NSDictionary (MyBookCategory)

- (bool) hasIntroduction {
    NSString *value = [self objectForKey:kHasIntroductionKey];
    return (value != nil && [value boolValue] == YES);
}

- (NSString*) getIntroductionLabel {
    return [self objectForKey:kIntroductionLabelKey];
}

- (bool) hasIntroductionItems {
    return [self objectForKey:kIntroductionItemsKey] != nil;
}

- (NSInteger) getIntroductionItemCount {
    return [[self objectForKey:kIntroductionItemsKey] count];
}

- (NSString*) getIntroductionItemForRow:(NSInteger) row {
    return [[self objectForKey:kIntroductionItemsKey] objectAtIndex: row];
}
// etc.
@end


I'm having a heck of a time figuring out the sytnax for this in Swift 2. I am developing a fairly good sense of how and when to use optionals and cast from AnyObject? into the desired type (modulo tripping frequently). Where I am having difficulty is with the mechanics of extending Dictionary. I simply don't know the right way. Let's walk naively through the scenarios I've tried. First, I tried this:


extension Dictionary {
    func hasIntroduction() -> Bool {
        let value = self[kHasIntroductionKey]


By line 3, I got my first compiler error, "Cannot subscript a value of type 'Dictionary<Key, Value>' with an index of type 'String'." Aha! Key and Value. Hmm. So, I tried casting the String key as Key:


        let value = self[kHasIntroductionKey as! Key]


And, lo and behold, the compiler stopped complaining. However, just becuase the compiler is happy, it doesn't mean the code is correct, or good (or maintainable), and this just feels wrong. Since this feels wrong, I next tried figuring out how to specialize the extension for keys of String and values of AnyObject. I tried this:


extension Dictionary<String, AnyObject> {


But that line gave me an error, "Constrained extension must be declared on the unspecialized generic type 'Dictionary' with constraints specified by a 'where' clause." OK, cool. I get to learn how to implemetn a 'where' clause. With this hint, I guessed wildly:


extension Dictionary where Key==String {


But then that line gave me an error "Same-type requirement makes generic parameter 'Key' non-generic." OK, so, now I am stumped. I'm not sure what to try next, step into the morass of extending CollectionType, SequenceType, etc.? If anyone has tread down this path already, and has a clear direction to offer me, please let me know. Thanks in advance!

Replies

How is this?

extension Dictionary where Key: NSObject, Value: AnyObject {
    func hasIntroduction() -> Bool {
        if let value = self[kHasIntroductionKey as! Key] as? NSString {
            return value.boolValue
        }
        return false
    }
}

This is helpful, and your suggestion is encouraging me to lean towards a non-pure solution, so thank you for that 🙂. Unfortunately, the code above crashes in my use case (feeding it a dictionary read from a property list file), and it still has some of the problems I'm seeking to avoid, namely, the "as! Key" cast, and frequent use of NS objects, however I think I'm ready to give up the ghost on the latter.


I should have been clearer about the code that works, but feels wrong, it was this:


extension Dictionary {
    func hasIntroduction() -> Bool {
        if let value = self[kHasIntroductionKey as! Key] as? NSString {
            return value.boolValue
        }
        return false
    }
}


The following alternative extension, on NSDictionary, eliminates the "as! Key" conversion, and is what I am currently leaning towards:


extension NSDictionary {
    func hasIntroduction() -> Bool {
        if let value = self[kHasIntroductionKey] as? NSString {
            return value.boolValue
        }
        return false
    }
}


I'm still holding out hope that there is a more general Swift solution that uses a where clause and native types (e.g., String).


Anyone who has read this far and thinks I should stop here (i.e., this is as close as it's going to get), feel free to chime in. Anyone who knows of a more general way to extend Dictionary for property list data, also feel free to chime in. Thanks in advance!