JSON Parsing in Swift 3 JSON Parsing in Swift 3 swift swift

JSON Parsing in Swift 3


Have you tried JSONSerialization.jsonObject(with:options:)?

var jsonString = "{" +    "\"Language\": {" +    "\"Field\":[" +    "{" +    "\"Number\":\"976\"," +    "\"Name\":\"Test\"" +    "}," +    "{" +    "\"Number\":\"977\"," +    "\"Name\":\"Test\"" +    "}" +    "]" +    "}" +    "}"var data = jsonString.data(using: .utf8)!let json = try? JSONSerialization.jsonObject(with: data)

Swift sometimes produces some very odd syntax.

if let number = json?["Language"]??["Field"]??[0]?["Number"] as? String {    print(number)}

Everything in the JSON object hierarchy ends up getting wrapped as an optional (ie. AnyObject?). Array<T> subscript returns a non-optional T. For this JSON, which is wrapped in an optional, array subscript returns Optional<AnyObject>. However, Dictionary<K, V> subscript returns an Optional<V>. For this JSON, subscript returns the very odd looking Optional<Optional<AnyObject>> (ie. AnyObject??).

  • json is an Optional<AnyObject>.
  • json?["Language"] returns an Optional<Optional<AnyObject>>.
  • json?["Language"]??["Field"] returns an Optional<Optional<AnyObject>>.
  • json?["Language"]??["Field"]??[0] returns an Optional<AnyObject>.
  • json?["Language"]??["Field"]??[0]?["Number"] returns an Optional<Optional<AnyObject>>.
  • json?["Language"]??["Field"]??[0]?["Number"] as? String returns an Optional<String>.

The Optional<String> is then used by the if let syntax to product a String.


Final note: iterating the field array looks like this.

for field in json?["Language"]??["Field"] as? [AnyObject] ?? [] {    if let number = field["Number"] as? String {        print(number)    }}

Swift 4 Update

Swift 4 makes this all much easier to deal with. Again we will start with your test data (""" makes this so much nicer).

let data = """{  "Language": {    "Field":[          {          "Number":"976",          "Name":"Test"          },          {          "Number":"977",          "Name":"Test"          }       ]   }}""".data(using: .utf8)!

Next we can define classes around the objects used in your JSON.

struct Object: Decodable {    let language: Language    enum CodingKeys: String, CodingKey { case language="Language" }}struct Language: Decodable {    let fields: [Field]    enum CodingKeys: String, CodingKey { case fields="Field" }}struct Field: Decodable {    let number: String    let name: String    enum CodingKeys: String, CodingKey { case number="Number"; case name="Name" }}

The CodingKeys enum is how struct properties are mapped to JSON object member strings. This mapping is done automagically by Decodable.


Parsing the JSON now is simple.

let object = try! JSONDecoder().decode(Object.self, from: data)print(object.language.fields[0].name)for field in object.language.fields {    print(field.number)}


In Xcode 8 and Swift 3 id now imports as Any rather than AnyObject

This means that JSONSerialization.jsonObject(with: data) returns Any. So you have to cast the json data to a specific type like [String:Any]. Same applies to the next fields down the json.

var jsonString = "{" +    "\"Language\": {" +    "\"Field\":[" +    "{" +    "\"Number\":\"976\"," +    "\"Name\":\"Test1\"" +    "}," +    "{" +    "\"Number\":\"977\"," +    "\"Name\":\"Test2\"" +    "}" +    "]" +    "}" +"}"var data = jsonString.data(using: .utf8)!if let parsedData = try? JSONSerialization.jsonObject(with: data) as! [String:Any] {    let language = parsedData["Language"] as! [String:Any]    print(language)    let field = language["Field"] as! [[String:Any]]    let name = field[0]["Name"]!    print(name) // ==> Test1}

In practice you would probably want some specific field buried in the json. Lets assume it's the Name field of the first element of Field array. You can use a chain of unwraps like this to safely access the field:

var data = jsonString.data(using: .utf8)!if let json = try? JSONSerialization.jsonObject(with: data) as? [String:Any],    let language = json?["Language"] as? [String:Any],    let field = language["Field"] as? [[String:Any]],    let name = field[0]["Name"] as? String, field.count > 0 {    print(name) // ==> Test1} else {    print("bad json - do some recovery")}

Also you may want to check Apple's Swift Blog Working with JSON in Swift


Shoving JSON into a string manually is a pita. Why don't you just put the JSON into a file and read that in?

Swift 3:

let bundle = Bundle(for: type(of: self))    if let theURL = bundle.url(forResource: "response", withExtension: "json") {        do {            let data = try Data(contentsOf: theURL)            if let parsedData = try? JSONSerialization.jsonObject(with: data) as! [String:Any] {                grok(parsedData)            }        } catch {            print(error)        }    }