I've been learning the basics of JSON and I am trying to read data from a JSON file that I have written. The JSON file looks like this:
gradeBoundaries = {
"Subjects": [
{
"Title":"Biology HL",
"Boundaries":
{
1:[0,15],
2:[16,27],
3:[28,39],
4:[40,52],
5:[53,64],
6:[65,77],
7:[78,100]
}
}
]
}
The code that I am using to take the data from this file is as follows:
if let url = Bundle.main.url(forResource: "gradeBoundaries", withExtension: "json") {
do {
let jsonData = try Data(contentsOf: url)
do {
let jsonResult: [([String: String], [String: [Int : [Int]]] )] = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! [([String: String], [String: [Int : [Int]]] )] //the entire object
} catch {}
} catch {}
}
When I run the code above, everything works fine until this line:
let jsonResult: [([String: String], [String: [Int : [Int]]] )] = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! [([String: String], [String: [Int : [Int]]] )] //the entire object
As you can see I am trying to cast jsonResult as a fairly complicated data structure. I have tried many others including NSDictionary and Array but none of them seem to make a difference. Since I am so new to JSON I could just be misinterpreting the format of the data, and if anyone could point me in the right direction that would be great.
However, it is indeed nothing to do with the casting, then I am even more lost. This is the way that many SO answers have said to read the data, but it simply does not work for me.
I even tried switching between Data and NSData to no effect. I want to be able to break this data down into smaller pieces, but my program keeps on getting stuck on this line, so I need some help. Thanks!
EDIT
Editing the data type to Any did not allow the line to execute:
let jsonResult: Any = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! Any
EDIT: 31 Dec 2016
Trying to make it work as below was ineffective:
typealias JSONDictionary = [String: Any]
if let url = Bundle.main.url(forResource: "gradeBoundaries", withExtension: "json") {
do {
let jsonData = try Data(contentsOf: url)
if let jsonResult: JSONDictionary = try JSONSerialization.jsonObject(with: jsonData, options: []) as? JSONDictionary {
print("success!")
}
} catch {}
}
However, it seems like a really good idea, so I think there must be something seriously wrong about my JSON or I'm doing something really stupid in my code.
EDIT
The JSON that I have been using is apparently invalid. I modified it now to be this:
{
"Subjects": [
{
"Title":"Biology HL",
"Boundaries":
{
1:[0,15],
2:[16,27],
3:[28,39],
4:[40,52],
5:[53,64],
6:[65,77],
7:[78,100]
}
}
]
}
First of all a message to all writers of online tutorials:
Please stop suggesting the reading option .mutableContainers in Swift. It's completely meaningless because mutability is provided for free by assigning the object to a variable.
Don't confuse yourself by annotating that weird snake-type.
To understand the structure read the hierarchy. [] is an array, {} a dictionary.
For convenience declare a type alias for a JSON dictionary
typealias JSONDictionary = [String:Any]
Then walk through the JSON (assuming the root object is a dictionary):
do {
if let jsonResult = try JSONSerialization.jsonObject(with:jsonData, options: []) as? JSONDictionary {
if let subjects = jsonResult["Subjects"] as? [JSONDictionary] {
for subject in subjects {
print(subject["Title"] as? String)
if let boundaries = subject["Boundaries"] as? JSONDictionary {
for (key, value) in boundaries {
print(key, value)
}
}
}
}
}
} catch {
print(error)
}
Regarding the JSON the keys in the Boundary dictionary must be strings:
{
"Subjects": [
{
"Title":"Biology HL",
"Boundaries":
{
"1":[0,15],
"2":[16,27],
"3":[28,39],
"4":[40,52],
"5":[53,64],
"6":[65,77],
"7":[78,100]
}
}
]
}
So I figured it out and feel like an idiot. After using Java and Swift for the longest time, I thought I would leave some comments on my JSON.
The JSON I was actually using was this:
{
"Subjects": [//[([String: String], [String: [Int : [Int]]] )]
{
"Title":"Biology HL", //[String: String]
"Boundaries": //[String: [Int: [Int]]]
{
"1":[0,15], //[Int: [Int]]
"2":[16,27],
"3":[28,39],
"4":[40,52],
"5":[53,64],
"6":[65,77],
"7":[78,100]
}
}
]
}
However, I thought that I would leave the comments out of the SO question because they weren't necessary.
After copying #vadian's suggested JSON my code worked. I then came up with this hypothesis and tried adding a comment to his JSON, and needless to say it didn't work.
So I guess the moral of this story is read up about commenting on code before you actually do it.
Related
I'm trying to parse some json in Xcode that is basically a bunch of objects in an object. The json looks like this below.
{"TDOC": {
"assetType": "EQUITY",
"assetMainType": "EQUITY",
"cusip": "87918A105",
"symbol": "TDOC"}}
I am parsing it using the code below in Xcode using swift5
do {
if let json = try JSONSerialization.jsonObject(with: jData, options: []) as? [String: Any] {
if let pr = json["TDOC"] as? Array<Dictionary<String, Any>> {
for p in pr {
print(p["assetType"] ?? "")
}
}
}
} catch let err {
print(err.localizedDescription)
}
I'm trying to get the assetType value but when I run the code, nothing prints out to the console. When I debug, it seems like Xcode just skips right over my for in loop for that prints the assetType for some reason. Any help on this is appreciated.
You can't treat TDOC object as an Array. As it is a dictionary object, you can take it as Dictionary directly.
You can do it like this.
do {
if let json = try JSONSerialization.jsonObject(with: jData, options: []) as? [String: Any] {
if let pr = json["TDOC"] as? Dictionary<String, Any> {
print(pr["assetType"])
}
}
} catch let err {
print(err.localizedDescription)
}
Try this.
"TDOC" key corresponds to a dictionary value, not an array. No for loop needed as well.
do {
if let json = try JSONSerialization.jsonObject(with: jData, options: []) as? [String: Any] {
if let pr = json["TDOC"] as? [String: Any] {
print(pr["assetType"])
}
}
} catch let err {
print(err.localizedDescription)
}
Hope this helps.
1. Make a reflecting class of your data and make it conform to Codable protocol
import Foundation
// MARK: - Welcome
struct MyObject: Codable {
let tdoc: Tdoc
enum CodingKeys: String, CodingKey {
case tdoc = "TDOC"
}
}
// MARK: - Tdoc
struct Tdoc: Codable {
let assetType, assetMainType, cusip, symbol: String
}
1. Parse it using JSONDecoder:
do {
let myObject = try JSONDecoder().decode(MyObject.self, from: jsonData)
print(myObject.tdoc.assetType)
} catch {
print(error)
}
heres an apple doc for a broader info about Encoding, Decoding and CodingKeys protocols
https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
Hope this helps :)
I wanted to know the best way to parse json response of below type in Swift 4. Response is double encoded -
\"[{\\"value\\":\\"International University \\\\"MITSO\\\\"\\",\\"id\\":\\"a1v24000000uOrPAAU\\",\\"addlFields\\":[\\"Mi?narodny Universitet \\\\"MITSO\\\\"\\"]}]\"
Here is the data in NSData format -
(String) $R0 = "data: Optional(146 bytes) as NSData: <225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>"
As you see value of the key "value" has a inner double quotes(").
JSONSerialization consider this as invalid Json.
Any help will be greatly appreciated.
The content of your data as String is as follows:
"[{\"value\":\"International University \\\"MITSO\\\"\",\"id\":\"a1v24000000uOrPAAU\",\"addlFields\":[\"Mi?narodny Universitet \\\"MITSO\\\"\"]}]"
Seeing the actual content without extra double-quotes and backslashes needed to show String as String-literal, it looks like some valid JSON is embedded in a String.
This may happen when the server side code double-encodes the data. You should better tell your server side engineer to fix the issue, but if it is difficult or would take long time, you can double-decode it.
Testing code:
import Foundation
let dataStr = "<225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>".dropFirst().dropLast().replacingOccurrences(of: " ", with: "")
let byteArr = stride(from: 0, to: dataStr.count, by: 2).map{(index: Int)->UInt8 in
let start = dataStr.index(dataStr.startIndex, offsetBy: index)
let end = dataStr.index(start, offsetBy: 2)
return UInt8(dataStr[start..<end], radix: 16)!
}
let responseData = Data(bytes: byteArr)
print(responseData as NSData)
Check here, whether the print statement output is exactly the same as your sample response. (If you want to test the following code with your actual data than sample response, use just let responseData = result as! Data instead of above lines.)
So, you just need to use JSONSerialization twice:
block: do {
let firstDecoded = try JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as! String
let firstDecodedData = firstDecoded.data(using: .utf8)!
let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData)
//Code below is an example of using decoded result.
guard let resultArray = secondDecoded as? [[String: Any]] else {
print("result is not an Array of Dictionary")
break block
}
print(resultArray)
if
let addlFields = resultArray[0]["addlFields"] as? [String],
let firstAddl = addlFields.first
{
print(firstAddl)
}
} catch {
print(error)
}
Outputs: (Omitting some output for print(responseData as NSData).)
[["id": a1v24000000uOrPAAU, "value": International University "MITSO", "addlFields": <__NSSingleObjectArrayI 0x100e40c80>(
Mi?narodny Universitet "MITSO"
)
]]
Mi?narodny Universitet "MITSO"
(You may find some parts like <__NSSingleObjectArrayI 0x100e40c80> are strange, but it's just a problem of generating default description and you can access the elements as an Array.)
Anyway, please try and see what you can get with my code above.
#OOPer thank you for the solution. Appreciate you giving your time.
Solution worked as expected. Pasting code here which may help others.
Here is how I am doing -
func getData(text:String, callback:#escaping (_ result: Array<somedata>?,_ error:Error?) -> Void) {
let params = ["search":text]
getDataSomeAPI(url: "http:\\xyz.com\fdf", params: params) { (result, error) in
if error == nil {
do {
//Response is double encoded
if let firstDecoded = try JSONSerialization.jsonObject(with: result as! Data, options: .allowFragments) as? String
{
let firstDecodedData = firstDecoded.data(using: .utf8)!
if let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData) as? NSArray {
var array = [somedata]()
for obj in secondDecoded {
Mapper<somedata>().map(JSONObject: obj).then { mappedObj in
array.append(mappedObj)
}
}
callback(array,nil)
}
}
}
catch {
//Handle unexpected data format
let error = NSError(domain: "",
code: 0,
userInfo: nil)
let sErr = Error(err: error)
callback(nil, sErr)
}
} else {
callback(nil, error)
}
}
}
I'm trying to parse JSON Data using Swift from Guidebox. An example of the data is like this:
{
"results": [
{
"id": 14673,
"title": "The Golden Girls",
"alternate_titles": [
"Golden Palace"
],
"container_show": 0,
"first_aired": "1985-09-14",
"imdb_id": "tt0088526",
"tvdb": 71292,
"themoviedb": 1678,
"freebase": "\/m\/01fszq",
"wikipedia_id": 217200,
"tvrage": {
"tvrage_id": 5820,
"link": "http:\/\/www.tvrage.com\/shows\/id-5820"
},
"artwork_208x117": "http:\/\/static-api.guidebox.com\/120214\/thumbnails_small\/14673-9570342022-208x117-show-thumbnail.jpg",
"artwork_304x171": "http:\/\/static-api.guidebox.com\/120214\/thumbnails_medium\/14673-3759246217-304x171-show-thumbnail.jpg",
"artwork_448x252": "http:\/\/static-api.guidebox.com\/120214\/thumbnails_large\/14673-2249565586-448x252-show-thumbnail.jpg",
"artwork_608x342": "http:\/\/static-api.guidebox.com\/120214\/thumbnails_xlarge\/14673-6064109057-608x342-show-thumbnail.jpg"
}
],
"total_results": 1,
"development_api_key": "You are currently using a temporary development API key. This API key is for testing only. You have used 57 of 250 available calls. Please see the API docs (http:\/\/api.guidebox.com\/apidocs) for additional information or to generate a production API key."
}
It seems that for my case, the simplest way to use the data would be to convert it to [String: Any], as all I really need is "id", "title", and an artwork value. However, all of the (countless) methods I have used are failing because of the fact that "alternate_titles" gets parsed as an NSArray and it makes everything more difficult.
So far I've tried variations of this method:
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String: Any]
let datafinal = jsonResult["results"] as! [String: Any]
//Fails around here usually, when the data is converted to [String: Any] or [Any:Any] because of the array.
let title = datafinal["title"]
} catch {
print("JSON Preocessing failed")
}
I've also used the SwiftyJSON library to try to convert the data to more easily readable JSON, but the methods to pull a dictionary from it always fail (I'm guessing due to the structure as well). Anyone have a simple method to get the JSON data from the URL and easily access the values in "results"? Any help is much appreciated!
Try this
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String: Any]
let datafinal = jsonResult["results"] as! NSArary
let title = datafinal[0].value(forKey: "title")
print("\(title)")
} catch {
print("JSON Preocessing failed")
}
All you need to do is access results as an array, and take the first element of the array, what is a Dictionary.
In the future, here is great tool, using what you can check the structure of your data more conveniently, and it might reveals problem like this faster.
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String: Any]
guard let results = jsonResult["results"] as? [Any], let resultDictinary = results.first as? [String: Any] else {
fatalError("Data structure invalid")
}
let title = resultDictinary["title"]
} catch {
print("JSON Preocessing failed")
}
I have this json data that I want to consume in Swift 3. I'm learning Swift and building a very basic app that displays the list of items in tableUIView from JSON.
{
"expertPainPanels" : [
{
"name": "User A",
"organization": "Company A"
},
{
"name": "User B",
"organization": "Company B"
}
]
}
I'm trying to get this data using Swift 3.
if (statusCode == 200) {
do{
let json = try? JSONSerialization.jsonObject(with: data!, options:.allowFragments) // [[String:AnyObject]]
/*
If I do this:
let json = try? JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:Any]
if let experts = json?["expertPainPanels"] as! [String: Any] {
I get "Initializer for conditional binding must have Optional type, not '[String: Any]'"
*/
// Type 'Any' has no subscript members.
if let experts = json["expertPainPanels"] as? [String: AnyObject] {
for expert in experts {
let name = expert["name"] as? String
let organization = expert["organization"] as? String
let expertPainPanel = ExpertPainPanel(name: name, organization: organization)!
self.expertPainPanels += [expertPainPanel]
self.tableView.reloadData()
self.removeLoadingScreen()
}
}
}catch {
print("Error with Json: \(error)")
}
}
It was working fine in Swift 2. I updated to Swift 3 which broke the code. I read several SO, but I still have hard time understanding it. I applied some suggestions including JSON Parsing in Swift 3, but I'm still unable to fix the error I'm getting.
As of Swift 3, you need to do a cast early on.
This line:
let json = try? JSONSerialization.jsonObject(with: data!, options:.allowFragments)
Should become this:
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as? [String : AnyObject]
This is because JSONSerialization now returns Any, which does not implement a variation for the [] operator. Make sure you safely unwrap the cast and take the common measures to ensure you don't crash your program.
Edit: Your code should more or less look like this.
let data = Data()
let json = try JSONSerialization.jsonObject(with: data, options:.allowFragments) as! [String : AnyObject]
if let experts = json["expertPainPanels"] as? [String: AnyObject] {
I am facing error while trying to parse JSON from a URL.
Following is the code:
override func viewDidLoad() {
super.viewDidLoad()
print("hello")
let url=NSURL(string:"http://jsonReports/sample_testkrtk252.json")
print("hello2")
if let allContactsData=NSData(contentsOfURL:url!)
{
print(allContactsData)
do{
let allContacts: AnyObject! = try NSJSONSerialization.JSONObjectWithData(allContactsData, options: NSJSONReadingOptions.MutableContainers)
print(allContacts)
if let json = allContacts as? Array<AnyObject> {
print(json)
for index in 0...json.count-1 {
let data12 : AnyObject? = json[index]
print(data12)
let collection = data12! as! Dictionary<String, AnyObject>
print(collection)
print(collection["data11"])
let data11 : AnyObject? = collection["data11"]
let cont : AnyObject? = collection["cont"]
data1.append(data11 as! String)
data2.append(cont as! String)
print(data1)
print(data2)
}
}
}
catch
{
print("error")
}
}
And after the successful run it gives the following output:
hello
hello2
<7b0d0a20 20202022 ... >
error
(lldb)
Also, it gives the error:
NSError Domain: "NSCocoaErrorDomain" - code: 3840 0x00007f8b62c9ef50 _userInfo __NSDictionaryI * 1 key/value pair 0x00007f8b62dcb3c0
It will be great if someone can look into this. Also, is there any better way to parse JSON in Swift?
your file .json is not a valid structure. Delete the first line:
"registration_id":APA91bEVsOgzkFFDCuEFn8PAS-FQqeaW6TRuz09CeKSnAJUSJmTvP8ubIsUkUe2zOzz8vk-FNqbNteOcD6m8m5nhrNWA96swZHYyX4nvY-mPCJTeBkEXTLuLwWCglbAUVCqJlwharLLJ,
Now try to run the code:
func run()->(Void){
//insert you file .jso into project. Or change the code
let filePath = NSBundle.mainBundle().pathForResource("sample_testkrtk252", ofType:"json")
let nsMutData:NSData = NSData(contentsOfFile:filePath!)!
do{
let allContacts:[String:AnyObject] = try NSJSONSerialization.JSONObjectWithData(nsMutData, options: NSJSONReadingOptions.MutableContainers) as! [String : AnyObject]
print(allContacts)
for temp in 0...allContacts.count-1 {
print(temp)
}
} catch
{
print("error")
}
}
Why not directly casting the AnyObject returned JSONObjectWithData to an array of NSDictionary? Like this:
let allContacts = try NSJSONSerialization.JSONObjectWithData(allContactsData, options: NSJSONReadingOptions.MutableContainers) as! [NSDictionary]
Are you sure your JSON is valid? Swift is very sensitive about that. Make sure all keys are quoted and you didn't forget any braces (especially the most outer ones).
Also for JSON parsing I would recommend SwiftyJSON which makes things a lot easier.
Thanks for your answers.
just editing the json worked for me.
yes, it is true that one needs to follow the format correctly, otherwise a simple double quote can disrupt the expected output. :)
"registration_id":"APA91bEVsOgzkFFDCuEFn8PAS-FQqeaW6TRuz09CeKSnAJUSJmTvP8ubIsUkUe2zOzz8vk-FNqbNteOcD6m8m5nhrNWA96swZHYyX4nvY-mPCJTeBkEXTLuLwWCglbAUVCqJlwharLLJ",
Thanks,
Poulami