I'm using NSJSONSerialization's JSONObjectWithData method to serialize json response data into a NSDictionary hierarchy, as part of my API handling in IOS. This works great. The long standing tedious part however is that when using the XCode debugger, the nested json data structure is not viewable in the variable watcher. This can best be seen in the photo at the end. The data is all accessible and navigable in the code, it just isn't for the debugger.
Is there any way to clean this up or serialize it better?
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (error){
NSLog(#"Request Error: %#", [error localizedDescription]);
if(completionHandler != nil){
completionHandler(nil,error);
}
} else {
NSError *jsonerror = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonerror];
if(jsonerror) {
NSLog(#"JSON Serialize Error: %#", [jsonerror localizedDescription]);
if(completionHandler != nil){
completionHandler(nil,jsonerror);
}
} else {
if(completionHandler != nil){
completionHandler(json,nil); // usual successful json route
}
}
}
}];
Try using AFNeteorking for serialization.
https://github.com/AFNetworking/AFNetworking
It might help.
If not you can print what ever you need in the console part of the debugger using po.
po object
For example
Related
Ok, I am now starting with iOS development and currently playing with NSData using Obj-C. Recently I'm using the URLSession:dataTask:didReceiveData method to get NSData using HTTP POST request. The server will be responding a JSON object containing a JSON array.
Something interesting is that when the response data is too large the NSLog part will print out: "The data couldn't be read because it isn't in the correct format". Below is the function:
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
NSError *error;
// get data from NSData into NSDict
NSDictionary *searchData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
NSArray *test = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableDictionary *mdict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSLog(#"what: %#", mdict);
NSLog(#"received data length: %#", [NSByteCountFormatter stringFromByteCount:data.length countStyle:NSByteCountFormatterCountStyleFile]);
if (error) {
NSLog(#"json error: %#", [error localizedDescription]);
}
}
Just wondering if anyone knows the potential reason causing this problem ??
[Update]
Well, an alternative to solve this problem might be using NSString for data storage. But would have to parse it by myself. I would prefer using NSDictionary though.
Beginner problem: didReceiveData is called when some of the data has arrived, before the data is complete. Read the documentation of didReceiveData, which explains what you need to do. You are trying to parse incomplete data. That won't work.
And when you are handling JSON data, you should never involve any NSString objects, except possibly to log data.
In my case i was hitting POST Method instead of GET. Solved my problem.
At first point you should check if the response received from the API is a valid JSON, can be check by online validators like jsonlint.com. If this is a vaid json, then you are good to go.
Further more you should evaluate the error object on every step when you get it to see which parsing of data cause this problem like:
NSError *error;
// get data from NSData into NSDict
NSDictionary *searchData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (error) {
NSLog(#"json error: %#", [error localizedDescription]);
}
NSArray *test = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableDictionary *mdict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSLog(#"what: %#", mdict);
NSLog(#"received data length: %#", [NSByteCountFormatter stringFromByteCount:data.length countStyle:NSByteCountFormatterCountStyleFile]);
if (error) {
NSLog(#"json error: %#", [error localizedDescription]);
}
Also, as you said that:
The server will be responding a JSON object containing a JSON array.
Are you sure it is returning NS(Mutable)Dictionary ?? Try printing it like this:
id response = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if (error) {
NSLog(#"json error: %#", [error localizedDescription]);
}
else
{
NSLog(#"json data : %#", response);
}
if the data is kind of josn ,you can
NSDictionary *dict= [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
or
[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding])
,so that you will be get nsdictionary or nsstring data, if you cannot parse it by yourself ,you can user jsonModel tranlte to model .
It may be a problem with your json data I have experienced the same. You have to serialize your json data like this below written code
NSDictionary *dict= [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
If you are populating json data from data base u have to use Nsarray.
Hope this will help you .
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
NSLog(#"Error");
} else {
NSError *e = nil;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &e];
if (!jsonDict) {
NSLog(#"Error parsing JSON: %#", e);
} else {
}
}
}];
Hi There,
I am trying to write a simple unit test to check if the parsing of the json was successful?
I have no experience with unit testing.
thanks
If you want to test parser method. Then you can create a dummy jsonDict in Test Class. Call you parser method with that jsonDict.
-(void) testParserMethod {
// jsonDict = {} // fill according to your method need
// let say our parser method require a dictionary containing name and age. and set our object. then we can create a dict like this.
NSDictionary jsonDict= #{ #"name" : #"name",
#"age" : #"age",
};
YourObject object = [YourParserClass parseMethed:jsonDict]
XCTAssertEqual(object.name, jsonDict[#"name"])
XCTAssertEqual(object.age, jsonDict[#"age"])
}
If object is set correctly with the dummy jsonDict that we provide. then the test case will pass.
I have since changed my implementation to not do this anymore, but I would be interested in knowing if the following could lead to issues - take for example a process that has multiple steps, each involving passing a reference to an NSError object. If you were to reuse this error object each step along the way, would that reference be negatively effected in some way, perhaps leading to some sort of memory management/dealloc crash?
Example (most of the in-between code has been omitted)
NSError *error;
NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error];
if (!data) {
return error;
}
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!responseData) {
return error;
}
NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
if (!resultDictionary) {
return error;
}
Even though the error is never "used" twice, meaning the error will be nil (it seems) the next time its reference is passed to another method, could the reference be effected in some way at any of the steps along the way?
Original code prior to editing in response to jlehr's comments:
NSError *error;
NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error];
if (error) {
return;
}
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
return;
}
NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
if (error) {
return;
}
EDIT: To anyone coming to this at a later date, the information jlehr is referring to can be found here:
When dealing with errors passed by reference, it’s important to test
the return value of the method to see whether an error occurred, as
shown above. Don’t just test to see whether the error pointer was set
to point to an error.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html
According to Apple's documentation, you should never check an NSError object returned by reference. Instead, check the return value of the init... method or factory method you're calling, for example:
NSError *error;
NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error];
if (data == nil) {
// Handle failure. It's safe to use the error object here.
}
However, your code doesn't appear to be doing anything with the NSError instance, (e.g., logging, or displaying an alert), so you can just pass NULL instead.
NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
if (data == nil) {
// Handle failure.
}
Here is what I want to parse.
{
"Words": {
"subjugate": "To conquer. ",
"contemplate": "To consider thoughtfully. ",
"comprise": "To consist of. ",
"pollute": "To contaminate. "
}
}
Here is a very stripped and over-simplified way to handle getting response data back from a server in JSON and serializing the JSON to an NSDictionary:
// this is just for example purposes, in this example the code is already running on a background thread so sending it synhcronously is OK, and I've already created the request object
NSError *error = nil;
NSHTTPURLResponse *response = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// if we don't have response data
if (!responseData) {
// handle that we got no response from the server
}
if (error) {
// handle the request error
}
// serialize the JSON result into a dictionary
NSError *serializationError = nil;
NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:0 &serializationError];
if (serializationError) {
// handle the error turning the data into dictionary
}
NSDictionary *wordsDictionary = resultDictionary[#"words"];
NSString *exampleValue = wordsDictionary[#"subjugate"]; // will be "To conquer." if everything goes to plan
You can parse as bellow
NSData *data = [#"{\"Words\":{\"subjugate\":\"To conquer.\",\"contemplate\":\"To consider thoughtfully.\",\"comprise\":\"To consist of.\",\"pollute\":\"To contaminate.\"}}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if(error)
{
NSLog(#"Error : %#", error);
}
else
{
NSLog(#"%#", json);
}
I tried the fallowing code to get access twitter timeline. It doesn't received any data from the server.What went wrong here?
ACAccount *twitterAccount=[arrayOfAccounts lastObject];
NSURL *requestURL=[NSURL URLWithString:#"http://api.twitter.com/1/statuses/user_timeline.json"];
NSMutableDictionary *parameters=[NSMutableDictionary new];
//[parameters setObject:#"100" forKey:#"count"];
//[parameters setObject:#"1" forKey:#"include_entities"];
SLRequest *post=[SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:requestURL parameters:parameters];
post.account=twitterAccount;
[post performRequestWithHandler:^(NSData *response, NSHTTPURLResponse *urlResponse, NSError *error) {
self.array=[NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&error];
if(self.array.count !=0)
NSLog(#"%#",self.array);
else
NSLog(#"No Data Recived");
Thanks in advance.
Twitter has advice to use Version 1.1 not advice v1. In version 1.1 api https,So try to use this url https://api.twitter.com/1.1/statuses/user_timeline.json instated of this url http://api.twitter.com/1/statuses/user_timeline.json. This work's fine.
Those NSError objects the API gives you? Their purpose is to tell you what went wrong. Use them.
Your problem is that you don't know what happened because you just try to convert to JSON. That is what could have went wrong:
the request failed (e.g. network problem)
you are not authorized to do whatever you did
the data returned is not actually JSON
the JSON object is not an array (would have lead to a crash).
To write defensive code (and that's what you want if you want to release this thing to the public) you have to check each of these steps to figure out what went wrong, so you can act accordingly. Yes, that will take more code, but less code is not always the best choice.
Code with better error handling would more look like this. Note how it checks the result of each step that could go wrong:
[post performRequestWithHandler:^(NSData *response, NSHTTPURLResponse *urlResponse, NSError *error) {
if (response) {
// TODO: might want to check urlResponse.statusCode to stop early
NSError *jsonError; // use new instance here, you don't want to overwrite the error you got from the SLRequest
NSArray *array =[NSJSONSerialization JSONObjectWithData:response options:NSJSONReadingMutableLeaves error:&jsonError];
if (array) {
if ([array isKindOfClass:[NSArray class]]) {
self.array = array;
NSLog(#"resulted array: %#",self.array);
}
else {
// This should never happen
NSLog(#"Not an array! %# - %#", NSStringFromClass([array class]), array);
}
}
else {
// TODO: Handle error in release version, don't just dump out this information
NSLog(#"JSON Error %#", jsonError);
NSString *dataString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSLog(#"Received data: %#", dataString ? dataString : response); // print string representation if response is a string, or print the raw data object
}
}
else {
// TODO: show error information to user if request failed
NSLog(#"request failed %#", error);
}
}];