11

I have an array of dictionaries that I'd like to convert to JSON. My object is of type [[String: AnyObject]] and would like to end up with a sample like this:

[
  { "abc": 123, "def": "ggg", "xyz": true },
  { "abc": 456, "def": "hhh", "xyz": false },
  { "abc": 789, "def": "jjj", "xyz": true }
]

This is what I'm trying, but the compiler is not liking my declaration:

extension Array where Element == Dictionary<String, AnyObject> {
    var json: String {
        do { return try? NSJSONSerialization.dataWithJSONObject(self, options: []) ?? "[]" }
        catch { return "[]" }
    }
}

How can I do this?

2
  • so your question isn't about JSON generation but constraining Element. Commented Sep 11, 2016 at 18:36
  • dataWithJSONObject returns NSData on success, so at least you have to create a string to return. Commented Sep 11, 2016 at 18:46

4 Answers 4

14

A simple way to achieve that is to just extend CollectionType.

Use optional binding and downcasting, then serialize to data, then convert to string.

extension CollectionType where Generator.Element == [String:AnyObject] {
    func toJSONString(options: NSJSONWritingOptions = .PrettyPrinted) -> String {
        if let arr = self as? [[String:AnyObject]],
            let dat = try? NSJSONSerialization.dataWithJSONObject(arr, options: options),
            let str = String(data: dat, encoding: NSUTF8StringEncoding) {
            return str
        }
        return "[]"
    }
}

let arrayOfDictionaries: [[String:AnyObject]] = [
    ["abc":123, "def": "ggg", "xyz": true],
    ["abc":456, "def": "hhh", "xyz": false]
]

print(arrayOfDictionaries.toJSONString())

Output:

[
  {
    "abc" : 123,
    "def" : "ggg",
    "xyz" : true
  },
  {
    "abc" : 456,
    "def" : "hhh",
    "xyz" : false
  }
]
Sign up to request clarification or add additional context in comments.

3 Comments

This will solve the asker's issue, but introduce subtle bugs, like dictionaries being converted to [] instead of their JSON equivalent. I think the asker also seeks for type safety, which would prevent runtime issues like this.
@Cristik Good thinking. I've added a type constraint to the extension. It should be closer to what OP wanted.
I'm running into Type of expression is ambiguous without more context errors when I create the arrayOfDictionaries. This may require a different question but it's making this solution not work...
10

Swift 4:

extension Collection where Iterator.Element == [String:AnyObject] {
    func toJSONString(options: JSONSerialization.WritingOptions = .prettyPrinted) -> String {
        if let arr = self as? [[String:AnyObject]],
            let dat = try? JSONSerialization.data(withJSONObject: arr, options: options),
            let str = String(data: dat, encoding: String.Encoding.utf8) {
            return str
        }
        return "[]"
    }
}

Usage:

let arrayOfDictionaries = [
  { "abc": 123, "def": "ggg", "xyz": true },
  { "abc": 456, "def": "hhh", "xyz": false },
  { "abc": 789, "def": "jjj", "xyz": true }
]

print(arrayOfDictionaries.toJSONString())

1 Comment

return error (key: String, value: Any)' is not convertible to '[String : Any]'
2

Swift 5, I had to change it to Any instead of AnyObject, building off solutions of Eric & A.G.

extension Collection where Iterator.Element == [String: Any] {
  func toJSONString(options: JSONSerialization.WritingOptions = .prettyPrinted) -> String {
    if let arr = self as? [[String: Any]],
       let dat = try? JSONSerialization.data(withJSONObject: arr, options: options),
       let str = String(data: dat, encoding: String.Encoding.utf8) {
      return str
    }
    return "[]"
  }
}

Usage:

let arrayOfDictionaries = [
  { "abc": 123, "def": "ggg", "xyz": true },
  { "abc": 456, "def": "hhh", "xyz": false },
  { "abc": 789, "def": "jjj", "xyz": true }
]

print(arrayOfDictionaries.toJSONString())

Comments

0

I think you're out of luck trying to extend Array, because same-type requirements are not possible when extending an entity.

What you can make is to make use of free functions:

func jsonify(array: [[String:AnyObject]]) { 
...
}

This way you'll keep the type safety you want to, with the expense of moving the function location.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.