1

I am retrieving JSON information for an API and it says on the API that it is in JSON but I noticed it is in JSONP or "json with padding" as some call it. I tired to look everywhere to find how to parse this but no luck. The information I am trying to receive is this:

 ({"book":[{"book_name":"James","book_nr":"59","chapter_nr":"3","chapter":
 {"16":{"verse_nr":"16","verse":"For where envying and strife is, there is confusion and 
 every evil work."}}}],"direction":"LTR","type":"verse"});

The link to the data is https://getbible.net/json?p=James3:16, so you can look at it directly.

This is the code I am using to try to retrieve the JSON Data and parse it into a NSMutableDictionary.

-(void)fetchJson {
    NSString *currentURL = [NSString stringWithFormat:@"https://getbible.net/json?p=James"];
    NSURL *url = [NSURL URLWithString:currentURL];
    NSData *data = [[NSData alloc]initWithContentsOfURL:url];
    NSURLRequest *theRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
    NSMutableData *receivedData = [[NSMutableData alloc] initWithLength:0];
    NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];

    [receivedData setLength:0];
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:url MIMEType:@".json" expectedContentLength:-1 textEncodingName:nil];
    expectedTotalSize = [response expectedContentLength];


    if ([data length] !=0) {
        NSLog(@"appendingData");
        [receivedData appendData:data];

        if(connection){
            NSLog(@"Succeeded! Received %lu bytes of data",(unsigned long)[receivedData length]);
        }

        NSError *error;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];

        if(jsonResponse){
            NSArray *responseArr = [jsonResponse mutableCopy];
            NSLog(@"%lu",(unsigned long)[responseArr count]);
        }else if (!jsonResponse){
            //do internet connection error response
        }
    }
 }

The results I am getting back from putting a breakpoint in the code is:

  • jsonResponse returns NULL
  • NSError NSCocoaErrorDomain code - 3840
  • but my NSData *data is returning 15640 bytes.

My console is displaying this from the NSLogs I used for debugging:

   2014-04-20 01:27:31.877 appendingData
   2014-04-20 01:27:31.879 Succeeded! Received 15640 bytes of data

I am receiving the data correctly but I am not parsing it correctly I know the error is because the JSON is in JSONP format. If anyone could please help with this I would appreciate it so much. I have tired to give as much detail on this question as I can but if you need more information just let me know so I can add it and make this as clear as possible.

lostAtSeaJoshua
  • 1,695
  • 2
  • 21
  • 34
  • Why do you use two separate approaches to downloading the data from the URL? – rmaddy Apr 20 '14 at 05:41
  • If you could point it out to me where I am doing that. This code for fetching JSON is something I complied from different resources so it may not be the most efficient code. – lostAtSeaJoshua Apr 20 '14 at 05:44
  • The line `NSData *data = [[NSData alloc]initWithContentsOfURL:url];` loads the content of the URL. This is easy but bad since it blocks the current thread. Then the code related to the `NSURLConnection` kicks off an asynchronous load of the data from the URL. You can't use that with your current setup since it is asynchronous. You need to implement all of the delegate methods. The best option is to replace logs of your code with `NSURLConnection sendAsynchronousRequest:queue:completionHandler:`. – rmaddy Apr 20 '14 at 05:48
  • So what should I take out and replace with NSURLConnection sendAsychronousRequest:queue:complentionHandler: – lostAtSeaJoshua Apr 20 '14 at 05:55
  • All of the code to read the data. Put your JSNO parsing code in the completion handler of `sendAsynchronousRequest:queue:completionHandler:`. – rmaddy Apr 20 '14 at 05:56
  • I really would not know how to do that. I do not know all the code related to the data. I understand what my code does but to be honest I do not know how step by step. – lostAtSeaJoshua Apr 20 '14 at 06:00
  • If you would like it would help out a lot if you explained the coding of it a little more so I could implement it. Either way thank you for pointing it out. – lostAtSeaJoshua Apr 20 '14 at 06:08

3 Answers3

6

Your code has at least two separate attempts to download the data. Neither is really correct. The code also only works with JSON, not JSONP.

Try this:

NSURL *url = [NSURL URLWithString:@"https://getbible.net/json?p=James"];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if (data) {
        NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSRange range = [jsonString rangeOfString:@"("];
        range.location++;
        range.length = [jsonString length] - range.location - 2; // removes parens and trailing semicolon
        jsonString = [jsonString substringWithRange:range];
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];

        NSError *jsonError = nil;
        NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&jsonError];
        if (jsonResponse) {
            // process jsonResponse as needed
        } else {
            NSLog(@"Unable to parse JSON data: %@", jsonError);
        }
    } else {
        NSLog(@"Error loading data: %@", error);
    }
}];
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Thank you so much! I do not have enough reputation points to up vote. Sorry. – lostAtSeaJoshua Apr 20 '14 at 06:14
  • I just tired this but noting happens at all. No errors or crashes, it just does not run. I am thinking you have if(data) but where is data created? – lostAtSeaJoshua Apr 20 '14 at 06:17
  • Also I would accept it but I do not think this answers the question about parsing JSONP data. – lostAtSeaJoshua Apr 20 '14 at 06:19
  • I just made a change to the `queue` parameter. Try that and see what happens. – rmaddy Apr 20 '14 at 06:21
  • It ran! That is way better code than what I had earlier, thank you so much but I still got the error from last time this is what the console returned: Unable to parse 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=0x8c99a40 {NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.} . Also I have no idea how to do the mini-Markdown formatting so sorry about it being hard to read. – lostAtSeaJoshua Apr 20 '14 at 06:24
  • OK, so all of this effort simply confirms that the data you are getting is not valid JSON and it can't be parsed like normal JSON. I've never worked with JSONP so you need to do some more research on parsing JSONP. Start here: http://stackoverflow.com/search?q=jsonp+%5Bobjective-c%5D – rmaddy Apr 20 '14 at 06:29
  • I just updated the answer again. These changes should allow you to parse the JSONP. – rmaddy Apr 20 '14 at 06:34
  • Yeah JSONP is so difficult to parse. I just ran your new updated edit and it returned this: Unable to parse JSON data: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Garbage at end.) UserInfo=0x8d85f90 {NSDebugDescription=Garbage at end.}. I have seen the code you added before and I see that it trying to get all the information inside of the parenthesis by skipping the function the JSON is wrapped in. – lostAtSeaJoshua Apr 20 '14 at 06:38
  • Change the `- 1` to `- 2`. I think the semicolon was being left on the end of the JSONP. – rmaddy Apr 20 '14 at 06:40
  • It worked!!! You are amazing. Thank you so much for all your help you helped me out twice with the JSONP parse and cleaning up my code. – lostAtSeaJoshua Apr 20 '14 at 06:42
3

One problem is that the data you're downloading has extraneous information at the beginning and end. The JSON being delivered by your URL is:

({"book":[{"book_name":"James","book_nr":"59","chapter_nr":"3","chapter":{"16":{"verse_nr":"16","verse":"For where envying and strife is, there is confusion and every evil work."}}}],"direction":"LTR","type":"verse"});

As the error message you're seeing indicates: you need to remove the initial ( from the beginning of the string and the ); from the end so that your JSON will start with the dictionary that your code expects. You can do this by calling subdataWithRange: on your NSData object:

NSData* jsonData = [data subdataWithRange:NSMakeRange(1, data.length-3)];
NSDictionary* jsonResponse = [NSJSONSerialization JSONObjectWithData:jsonData
                                                             options:0
                                                               error:&error];
0

Just to update everyone, the NSURLRequest has been deprecated in iOS9. I tried the answer by @rmaddy, and I didn't receive anything either (just like what @lostAtSeaJoshua was encountering I guess). I have updated rmaddy's answer to reflect the NSURLSession implementation that has (I think) replaced NSURLRequest:

    NSURLSession *session = [NSURLSession sharedSession];
    NSURL *url = [NSURL URLWithString:@"http://somerandomwebsite.com/get.php?anotherRandomParameter=5"];
    [[session dataTaskWithURL:url
            completionHandler:^(NSData *data,
                                NSURLResponse *response,
                                NSError *error) {
                // handle response
                if (data) {
                    NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                    NSLog(@"stringJSONed: %@",jsonString);

//Do something with the received jsonString, just like in @ rmaddy's reply
                } else {
                    NSLog(@"Error loading data: %@", error);
                }
            }] resume];

Just a heads up notice, when I first ran it, it gave me the security error. What you need to do (if you are using http) is to add this to your plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

I have to mention that after the NSAllowArbitraryLoads key, there are most probably other keys and values, such as NSExceptionDomain. But they're not really relevant to this answer I think. If you need to look further, let me know and I will dig deeper :)

Septronic
  • 1,156
  • 13
  • 32