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 anOptional<AnyObject>
.json?["Language"]
returns anOptional<Optional<AnyObject>>
.json?["Language"]??["Field"]
returns anOptional<Optional<AnyObject>>
.json?["Language"]??["Field"]??[0]
returns anOptional<AnyObject>
.json?["Language"]??["Field"]??[0]?["Number"]
returns anOptional<Optional<AnyObject>>
.json?["Language"]??["Field"]??[0]?["Number"] as? String
returns anOptional<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) } }