Parse JSON String and array with NSJSONSerialization issue? - objective-c

This is the code i have so far
// Parse data using NSJSONSerialization
NSError *error = nil;
NSArray *JsonArray = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error: &error];
if(!JsonArray)
{
NSLog(#"Error Parsing Data: %#", error);
}
else
{
for(NSDictionary *event in JsonArray)
{
if([[event description] isEqualToString:#"error"])
{
// Get error number? I am confused by this part
NSLog(#"Element: %#", [event objectForKey:#"error"]);
}
else
{
NSLog(#"Element: %#", [event description]);
}
}
}
this is the JSON Data that parses correctly:
[{data string}, {data strings}]
This only gives me the string "error" and not the int as well:
{"error":0}
I am echoing this data from a PHP script if that helps any. Am i just doing it wrong, or did i miss something?

Your problem is that when you receive an error, you get back an NSDictionary and not an NSArray. This should work:
if ([jsonObject isKindOfClass:[NSArray class]]) {
// no error: enumerate objects as you described above
} else if ([jsonObject isKindOfClass:[NSDictionary class]]) {
// error: obtain error code
NSNumber *errCode = jsonObject[#"error"];
} else {
// something bad's happening
}
Stylistic pieces of advice:
Don't call your object JsonArray, since it's not always an array. Call it jsonObject.
Don't start variable names with capital letters.

Would be great if you had posted the complete JSON document that you are trying to parse, because without that, there is absolutely no chance to figure out whether your code is anywhere near correct. The example [{data string}, {data strings}] that you gave is most definitely not a correct JSON document, so trying to parse it will return nil. {"error":0} is a dictionary with a single key "error" and a value 0. Having dictionaries with a single key is let's say unusual.
A JSON document contains either an array or object (using JSON terms) which will be turned either into an NSArray* or an NSDictionary*. You should know whether you expect an array or dictionary. If you expect an NSArray, check that [jsonObject isKindOfClass:[NSArray class]]. If you expect an NSDictionary, check that [jsonObject isKindOfClass:[NSDictionary class]]. If you don't do that then the wrong JSON document will either crash your app or produce total nonsense.
If you have an array then you will usually iterate through the elements of the array and handle each one in turn. If you have a dictionary you will usually look up keys that you know how to handle. What you are doing, iterating through an array of dictionaries, and checking for a dictionary with a key of "error", that's a very strange design of your JSON document.
And lookup what the "description" method does. "description" is what NSLog calls to find out what to print when it is asked to print an object. For an NSDictionary with a single key "error" and a value 0, it would return something like "error:0" which is of course not the same as "error".

NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:&error];
NSLog(#"jsonDic: %#", [jsonDic objectForKey:#"string"]);

Related

How to handle JSON exception objective c

Before I make url, which I will use to fetch json, user has to input some data first.
If the input data is wrong, the JSON will not be fetched properly.
But I cannot figure out how to handle that exception of calling JSON with wrong url.
this is my code:
NSError *error;
NSMutableDictionary* json = [NSJSONSerialization
JSONObjectWithData:url
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
if (error){
NSLog(#"%#",[error localizedDescription]);
}
else{
#try {
if (json){
[Constants shared].salt = json[#"salt"];
The last line of code is where the exception occurs, since the user had put the wrong input.
SO obviously, there wont be a proper json response fetchet, and there will be no "salt" object.
Error I get is:
-[__NSArrayM objectForKeyedSubscript:]: unrecognized selector sent to instance 0x146aa310
I tried putting json fetching in #try #catch, but it didn't work out.
Any suggestions ?
EDIT:
this is the json responce I get, when the user types in the right code:
{
user_id: "22066",
salt: "ce8c0f9e3e1add06bebc1acded7b692b68efddb87bfdc5bb1fb516f6a3e24425"
}
This is what i get, when the code is invalid:
[ ] (empty array)
The problem isn't that the dictionary lacks an object for the key #"salt", it's that json isn't a dictionary in the first place. Take a close look at the error message and you'll see that it's an array. The problem isn't the key, but the fact that arrays don't respond to -objectForKeyedSubscript:.
Accordingly, when you get an object back from -JSONObjectWithData:..., check that it's not nil and that it is in fact a dictionary before you try to access its contents. You can check it like this:
if ([json isKindOfClass:[NSDictionary class]]) {
// put the code that accesses `json` here
}
That condition will be false if json is nil or something other than a dictionary. You could add an else clause to take any necessary steps to recover if you're relying on getting the data.

How do I get this value from NSMutableArray?

this is a fairly simple question.
I am using a web service in my app, and the server returns a JSON string to communitcate with the app.
Here is an example response:
{
repsonse = {
message = "Message";
"response_id" = X;
};
}
Using objective-c I want to be able to get what "response_id" is but I am unsure on how to do this.
Here is my code:
NSMutableArray *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
// Get json value
NSLog(#"%#", json);
if([json[#"response"][#"response_id"] isEqualToString:#"1"]){
return YES;
}else{
return NO;
}
Each time the isStringEqualTo method returns false.
Could somebody help me?
Thanks,
Peter
You have two problems:
json needs to be declared as an NSDictionary, not NSMutableArray since the JSON root is a dictionary, not an array. And you get back an immutable dictionary, not a mutable one.
The JSON has a key of "repsonse", not "response".

ERROR happened while deserializing the JSON data

-(void) conn:(NSString *)method{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSDictionary *resultBlock = nil;
dispatch_sync(concurrentQueue, ^{
/* Download the json here */
//Create webservice address
NSString *webService = [_baseURL stringByAppendingString:_webService];
//NSLog(#"%#", webService);
//Create error object
NSError *downloadError = nil;
//Create the request
NSMutableURLRequest *req = [self initRequest:webService method:method];
if(req != nil){
//Request the json data from the server
NSData *jsonData = [NSURLConnection
sendSynchronousRequest:req
returningResponse:nil
error:&downloadError];
if(downloadError!=nil){
NSLog(#"DOWNLOAD ERROR %#", downloadError);
}
NSError *error = nil;
id jsonObject = nil;
if(jsonData !=nil){
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:kNilOptions
error: &error];
}
//Handel the deserialized object data
if (jsonObject != nil && error == nil){
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]){
resultBlock = (NSDictionary *)jsonObject;
//NSLog(#"Deserialized JSON Dictionary = %#", resultBlock);
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Deserialized JSON Array = %#", deserializedArray);
} else {
/* Some other object was returned. We don't know how to deal
with this situation, as the deserializer returns only dictionaries
or arrays */
}
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data. %#", error);
}else{
NSLog(#"No data could get downloaded from the URL.");
//[self conn:method];
}
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
/* Check if the resultBlock is not nil*/
if(resultBlock != nil){
/*Set the value of result. This will notify the observer*/
[self setResult:resultBlock];
}
});
});
}
Why do I get the following error?
An error happened while deserializing the JSON data. Error
Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be
completed. (Cocoa error 3840.)" (JSON text did not start with array or
object and option to allow fragments not set.) UserInfo=0x20839f80
{NSDebugDescription=JSON text did not start with array or object and
option to allow fragments not set.}
When I change it to
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:NSJSONReadingAllowFragments
error: &error];
}
I get the following error:
An error happened while deserializing the JSON data. Error
Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be
completed. (Cocoa error 3840.)" (Invalid value around character 0.)
UserInfo=0x20888760 {NSDebugDescription=Invalid value around character
0.}
I changed my connection from LTE to wifi and now I get
504 error and NSLog(#"No data could get downloaded from the URL.");
You should fix these issues in your code first:
Properly check for errors in methods which provide a pointer to a reference to an NSError object as the last parameter, e.g.: - (BOOL) doSomething:(NSError**)error, or -(NSData*) doSomething:(NSError**)error
In order test for an error correctly, you have to check the return value of the method only. Those methods indicate an error condition with a "special return value". For example, they return NO or nil - as always specified in the documentation. Only after the method indicated an error, the provided error parameter contains a meaningful value - that is, it points to an NSError object created by the method. Note that this parameter may also become none NULL when the method succeeded, in which case that has no "meaning".
Web services usually can provide several formats of the requested resource. If you don't specify which format you want the server to encode the resource, you get a default format - which is not necessarily JSON.
In order to be explicit about the desired format of the resource, set a corresponding "Accept" header. For example, if you wish the format in JSON you would set a header: "Accept: application/json" in your request.
Web services may have reasons not to respond with the resource you requested. In order to be sure you got the response that you requested, you need to check the response for status code and MIME type in order to ensure you actually received a JSON response.
It seems, you are a bit uncertain about how to use dispatch functions to your advantage. If you use the synchronous convenient method sendSynchronousRequest:... You certainly need to wrap it in only one dispatch_async function. If you then want to set the result on the main thread, you certainly want to use dispatch_async, not dispatch_sync.
However, it would be an improvement if you would use sendAsynchronousRequest:... instead. And only if you would use NSURLConnection in asynchronous mode and implement the NSURLConnection delegate methods - which I strongly recommend - it would actually become great ;)
So, I think, once you fixed your code, you may be able to answer the original question yourself, or get better error responses from the server, or the error magically disappeared ;)

Xcode json error

I'm learning Xcode at the moment and i have a project that is pulling data from a Mysql database using php and passing it to my app via json. In the database all varchars are set to utf8_bin.
here is the php:
header('Cache-Control: no-cache, must-revalidate');
header('Content-type: application/json');
echo json_encode($this->Idea_model->get($id));
here is a snipet of the outputted JSON:
[{"id":"1","title":"JWT blood sucka","objective":"test ","mission":"test","design_time":"80","development_time":"80","votes":"0","user_id":"0","date_created":"2012-08-03","date_modified":"2012-08-03","active":"1"},{"id":"2","title":"ford - liveDealer","objective":"to increce ","mission":"thid id a es","design_time":"80","development_time":"80","votes":"1","user_id":"1","date_created":"0000-00-00","date_modified":"0000-00-00","active":"1"}]
in xcode I'm using this function to pull in the JSON [reference tutorial:
http://www.raywenderlich.com/5492/working-with-json-in-ios-5]
(void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSArray* latestLoans = [json objectForKey:#"loans"]; //2
NSLog(#"loans: %#", latestLoans); //3
}
when i use this JSON file from the tutorial it works
http://api.kivaws.org/v1/loans/search.json?status=fundraising
but when i use my JSON file i get the following error.
[8690:207] -[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x6a10400
Current language: auto; currently objective-c
obviously there is an issue with my JSON output as i printed the contents from the tutorial file in my PHP file and that worked as well.
i also have tried "reset contents and settings" in the iOS simulator.
any ideas?
The returned object appears to be an array but your code is treating it like a dictionary (json object/hash)
The error tells you this: it say that the message objectForKey: (which is a method on NSDictionary) is being sent to an instance of __NSCFArray, which is an implementation class of NSArray, hence my supposition...
Yes I have an Idea -
-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x6a10400
Arrays are not dictionaries. They do not respond to objectForKey
They respond to objectForIndex;
You are thinking you have an array when you have a dictionary.
Common JSON mistake.
Heres your data:
its a list
starts here --> "[" then the object starts here "{"
[{"id":"1","title":"JWT blood sucka","objective":"test ","mission":"test","design_time":"80","development_time":"80","votes":"0","user_id":"0","date_created":"2012-08-03","date_modified":"2012-08-03","active":"1"}
then a comma "," then the next item in the list starting with a { "{"id":"2","title":"ford - liveDea
JSON says a list is an array and an object is a dictionary so flip your code around
(void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSArray* latestLoans = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSLog(#"loans: %#", latestLoans); //3
for (int i=0; i < latestLoans.count; i++)
{
NSDictionary *myLoan = (NSDictionary*)[latestLoans objectAtIndex:i];
NSLog(#"loan:%#", myLoan);
}
....
Got it?

NSError: Does using nil to detect Error actually turn off error reporting?

I got into the habit of coding my error handling this way:
NSError* error = nil;
NSDictionary *attribs = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
if (error != nil) {
DLogErr(#"Unable to remove file: error %#, %#", error, [error userInfo]);
return;
}
But looking at the documentation It seems like I got this wrong.:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you do not want error information.
Technically there is no difference between nil and NULL so does this mean I'm actually turning this off and will never get a error message (even if the delete in the above example did fail) ?
Is there a better way to code this ?
Thanks.
First off, the following line doesn't really make sense:
NSDictionary *attribs = [[NSFileManager defaultManager]
removeItemAtPath:fullPath error:&error];
-removeItemAtPath:error: returns a BOOL value, not a dictionary.
I think I see what you’re wondering about with the NULL value. Notice carefully though, how there are 2 *'s in the error parameter in the method signature:
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
That means a pointer to a pointer. When you pass in &error, you are passing in the address of the pointer to the NSError. (Ugh, someone else can probably help me out here, as my head still starts to swim when dealing with pointers to pointers). In other words, even though you have set error to nil, you aren't passing in error to the method, you're passing in &error.
So, here’s what the re-written method should look like:
// If you want error detection:
NSError *error = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:fullPath
error:&error]) {
NSLog(#"failed to remove item at path; error == %#", error);
// no need to log userInfo separately
return;
}
// If you don't:
if (![[NSFileManager defaultManager] removeItemAtPath:fullPath
error:NULL]) {
NSLog(#"failed to remove item at path");
return;
}
Passing NULL means the following:
BOOL itemRemoved = [[NSFileManager defaultManager] removeItemAtPath:fullPath
error:NULL];
i.e., the error parameter is NULL. Internally, -removeItemAtPath:error: sees if a valid pointer was passed. If it’s NULL, it simply won’t report the error as an NSError instance — but the return value will indicate whether the method completed successfully.
Also, your test is wrong. You shouldn’t be using the error output parameter to detect if an error occurred because it might be set even if the method completes successfully. Instead, you should use the return value of the method to detect errors. If the return value (in this particular case) is NO, then use the error output parameter to get information about the error:
NSError *error = nil;
BOOL itemRemoved = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error];
if (itemRemoved == NO) {
DLogErr(#"Unable to remove file: error %#, %#", error, [error userInfo]);
return;
}
Quoting the Error Handling Programming Guide,
Important: Success or failure is indicated by the return value of the method. Although Cocoa methods that indirectly return error objects in the Cocoa error domain are guaranteed to return such objects if the method indicates failure by directly returning nil or NO, you should always check that the return value is nil or NO before attempting to do anything with the NSError object.
Edit: As NSGod pointed out, -removeItemAtPath:error: returns BOOL, not NSDictionary *. I’ve edited my answer to reflect that as well.
No I do it the same way and it works just fine for detecting errors. You are not passing NULL to it you are passing a pointer to NULL to it which is a very different thing. Although another option you might want to add is.
if (error != nil){...
}else{
[NSApp presentError:error]
}