52

I am trying to create a dictionary extension where Dictionary is of the type <String, AnyObject>.

Was looking in many places and trying different approaches, but none of them seemed to work. This was one of them:

extension Dictionary where <String, AnyObject>{
    var jsonString:String {
        return ""
    }
}

Another method that didn't actually work for some reason:

extension Dictionary where Key:Hashable, Value:AnyObject {

    var jsonString:String {

        do {
           let stringData = try NSJSONSerialization.dataWithJSONObject(self, options: NSJSONWritingOptions.PrettyPrinted)
            if let string = String(data: stringData, encoding: NSUTF8StringEncoding){
                return string
            }
        }catch _ {

        }
        return ""
    }
}

Got: Argument type 'Dictionary' does not conform to expected type of 'AnyObject'

Reid Ellis
  • 3,996
  • 1
  • 21
  • 17
Andrius Steponavičius
  • 8,074
  • 3
  • 22
  • 25

7 Answers7

118

>=3.1

From 3.1, we can do concrete extensions, ie:

extension Dictionary where Key == String {}

<3.1

We can not conform concrete types w/ concrete generics, ie:

extension Dictionary where Key == String

However, because Dictionary conforms to sequence and we can conform protocol types w/ concrete generics, we could do:

extension Sequence where Iterator.Element == (key: String, value: AnyObject) {
    func doStuff() {...

Otherwise, we can constrain our key to a protocol that string conforms to like this:

extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
    var jsonString: String {
        return ""
    }
}

As per your updated answer. Json serialization needs an object, Swift Dictionaries are structs. You need to convert to an NSDictionary You must specify Key to conform to NSObject to properly convert to an NSDictionary.

Small note: Dictionaries already type constrain Key to be Hashable, so your original constraint wasn't adding anything.

extension Dictionary where Key: NSObject, Value:AnyObject {

    var jsonString:String {

        do {
            let stringData = try NSJSONSerialization.dataWithJSONObject(self as NSDictionary, options: NSJSONWritingOptions.PrettyPrinted)
            if let string = String(data: stringData, encoding: NSUTF8StringEncoding){
                return string
            }
        }catch _ {

        }
        return ""
    }
}

Note, that the dictionaries must conform to this type to access the extension.

let dict = ["k" : "v"]

Will become type [String : String], so you must be explicit in declaring:

let dict: [NSObject : AnyObject] = ["k" : "v"]
Logan
  • 52,262
  • 20
  • 99
  • 128
  • 1
    This is what I get with your code: `error: type 'Key' constrained to non-protocol type 'String'` – Martin R Sep 16 '15 at 13:42
  • @MartinR - Thanks for pointing it out, I swapped it for `StringLiteralConvertible`. I think this possibly satisfies the intent. – Logan Sep 16 '15 at 13:49
  • Yeah basically I should specify protocols not types, found list of protocols that really helped me https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/#protocols – Andrius Steponavičius Sep 16 '15 at 13:51
  • @AndriusSteponavičius - See above – Logan Sep 16 '15 at 14:40
  • Your dictionary needs to conform to the type constraints: `let dict: [NSObject : AnyObject] = ["k" : "v"]`. The other option is to attempt a cast within like `if let convertible = self as? [NSObject : AnyObject]` then use that to make NSDictionary `convertible as NSDictionary` – Logan Sep 16 '15 at 14:47
  • @AndriusSteponavičius - Is there a reason you unaccepted this answer? I believe it answers your original question, as well as your additional appended questions. – Logan Sep 16 '15 at 14:54
  • I believe its almost answered but if it takes to declare dictionary in an intuitive way and use extension in slightly unnatural way I will try to find a nicer solution, if I wont be able to I will accept it as answer – Andrius Steponavičius Sep 16 '15 at 14:58
  • @BenLeggiero what about `Key: AnyHashable`? – Logan Nov 14 '16 at 16:50
  • @Logan `:0: error: type 'Key' constrained to non-protocol type 'AnyHashable'` – Ky - Nov 14 '16 at 16:53
8

Update for Swift 3
Here is my example using ExpressibleByStringLiteral for Key and Any for Value.

extension Dictionary where Key: StringLiteralConvertible, Value: Any {
    var jsonString: String? {
        if let dict = (self as AnyObject) as? Dictionary<String, AnyObject> {
            do {
                let data = try JSONSerialization.data(withJSONObject: dict, options: JSONSerialization.WritingOptions(rawValue: UInt.allZeros))
                if let string = String(data: data, encoding: String.Encoding.utf8) {
                    return string
                }
            } catch {
                print(error)
            }
        }
        return nil
    }
}

and then I use it like this:

let dict: Dictionary<String, AnyObject> = [...]
let jsonString = dict.jsonString

You can convert self to AnyObject or NSObject, both works, then you do unwrap as Dictionary or any other specific type.

Jonauz
  • 4,133
  • 1
  • 28
  • 22
8

Swift 3 Approach

extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject

As StringLiteralConvertible is now deprecated and replaced by ExpressibleByStringLiteral

Aidan Malone
  • 400
  • 3
  • 7
5

Adding to the answer provided by @Logan, for those looking to add custom properties to the string-subscripted Dictionary, that is possible as well (was looking to do this when I came across this SO question):

extension Dictionary where Key: StringLiteralConvertible {

    var somePropertyThatIsAColor:UIColor? {
        get {
            return self["someKey"] as? UIColor
        }
        set {
            // Have to cast as optional Value
            self["someKey"] = (newValue as? Value)
    }

}
5

Anyone using [String:Any] instead of Dictionary can use below extension

extension Dictionary where Key == String, Value == Any {

    mutating func append(anotherDict:[String:Any]) {
        for (key, value) in anotherDict {
            self.updateValue(value, forKey: key)
        }
    }
}
Dhaval H. Nena
  • 3,992
  • 1
  • 37
  • 50
3

So, Dictionary is:

public struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible {..}

How about a protocol extension? :D

extension CollectionType where Self: DictionaryLiteralConvertible, Self.Key == String, Self.Value == AnyObject, Generator.Element == (Self.Key, Self.Value) {
...
}
swiftBoy
  • 35,607
  • 26
  • 136
  • 135
Andrei Popa
  • 159
  • 9
2

I was not able to make any of the offered solutions work in Swift 3, but by taking advantage of bridging between Dictionary and NSDictionary I could make this work:

extension NSDictionary {

    var jsonString:String {

        do {
            let stringData = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
            if let string = String(data: stringData, encoding: .utf8) {
                return string
            }
        }catch _ {

        }
        return ""
    }
}
Drew C
  • 6,408
  • 3
  • 39
  • 50
  • Seems swift 3 doesn't support Dictionary extensions out of the box. If you add this: ``protocol AnyDict{} extension Dictionary:AnyDict{}`` and then do ``extension AnyDict{}`` You can at least drop NSDictionary out of the equation. Casting via ``as! Dictionary`` may apply inside extension methods – Sentry.co Feb 24 '17 at 16:12