Has anyone been able to find a way to parse through JSON files in Swift 3? I have been able to get the data to return but I am unsuccessful when it comes to breaking the data down into specific fields. I would post sample code but I've gone through so many different methods unsuccessfully and haven't saved any. The basic format I want to parse through is something like this. Thanks in advance.
{
"Language": {
"Field":[
{
"Number":"976",
"Name":"Test"
},
{
"Number":"977",
"Name":"Test"
}
]
}
}
This answer was last revised for Swift 5.3 and iOS 14.4 SDK. Given some already obtained JSON data, you can use JSONDecoder to decode it into your Decodable model (or a collection of models). Such model must conform to the Decodable protocol and contain correct mapping between properties and JSON dictionary keys.
The first step to convert a JSON object to a Swift type is to create a model. For our example above, here's a struct we can use: As you can see, each JSON key will be represented by the property in the model above. Be sure to conform to the Codable protocol so it can be used to decode and encode JSON.
JSON stands for JavaScript Object Notation. It's a popular text-based data format used everywhere for representing structured data. Almost every programming language supports it with Swift being no exception. You are going to use JSON a lot throughout your career, so make sure you don't miss out.
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With