Cockpit Aliens
Cockpit Aliens

Reputation: 411

iOS Terminating app due to uncaught exceptions when parsing JSON object

This is a new error since I started learning to develop for iOS a week ago and I totally don't have a clue what is causing it and why the app suddenly crushes. There must be something wrong in my code if you may have a look at it and kindly suggest how to fix this run-time bug.

*** Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (1) beyond bounds (1)'
*** First throw call stack:
(0x2514c49f 0x32902c8b 0x2514c3e5 0x25079a6d 0xd01cb 0x2863f9fb 0x2863f9a1 0x2862a613 0x28747781 0x287fdc6b 0x286041ad 0x286390c1 0x2863899d 0x2860f15d 0x28882ab9 0x2860dbb9 0x25112d57 0x25112167 0x251107cd 0x2505e3c1 0x2505e1d3 0x2c45c0a9 0x2866dfa1 0xa3911 0x32e82aaf)
libc++abi.dylib: terminating with uncaught exception of type NSException

Here is my code snippet

- (IBAction)changeIns:(id)sender {
    UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
    NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
    
    if (selectedSegment == 0) {
        //toggle the correct view to be visible
        NSLog(@"Clicked");
        jsonData = [tArray objectAtIndex:0];
        self.displayInsurer.text=jsonData[@"insurerName"];
        
    }
    else if (selectedSegment == 1) {
        //toggle the correct view to be visible
        jsonData = [tArray objectAtIndex:1];
        self.displayInsurer.text=jsonData[@"insurerName"];
        
    }
    else if (selectedSegment == 2) {
        //toggle the correct view to be visible
        
        
    }
    else if (selectedSegment == 3) {
        //toggle the correct view to be visible
        
    }
    else if (selectedSegment == 4) {
        //toggle the correct view to be visible
        
    }
    else if (selectedSegment == 5) {
        //toggle the correct view to be visible
        
    }
    else if (selectedSegment == 6) {
        //toggle the correct view to be visible
        
    }
    else if (selectedSegment == 7) {
        //toggle the correct view to be visible
        
    }
    else if (selectedSegment == 8) {
        //toggle the correct view to be visible
        
    }
     
}

-(void)setVariableFromNetwork
{
    // NSInteger success = 0;
    @try {
        
        
        NSString *post =[[NSString alloc] initWithFormat:@"uid=%@&tag=getVehicleInfo",[[NSUserDefaults standardUserDefaults] stringForKey:@"userID"]];
        NSLog(@"PostData: %@",post);
        
        NSURL *url=[NSURL URLWithString:@"URL"];
        
        NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
        
        NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]];
        
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:url];
        [request setHTTPMethod:@"POST"];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:postData];
        
        //[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];
        
        NSError *error = [[NSError alloc] init];
        NSHTTPURLResponse *response = nil;
        NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
        
        NSLog(@"Response code: %ld", (long)[response statusCode]);
        
        if ([response statusCode] >= 200 && [response statusCode] < 300)
        {
            NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
            NSLog(@"Response ==> %@", responseData);
            
            NSError *error = nil;
            
            tArray = [NSJSONSerialization JSONObjectWithData:urlData options:kNilOptions error:&error];
            NSLog(@"objects %@", [tArray description]);
            
            NSLog(@"first object %@", [tArray objectAtIndex:0]);
            NSDictionary *jsonData = [tArray objectAtIndex:0];
            NSLog(@"dictionary %@", [jsonData description]);
            
            NSLog(@"vehicle %@", jsonData[@"vehicleMake"]);
            NSLog(@"Vehicle Number %@", jsonData[@"vehicleRegNo"]);
            int count = [tArray count];
            
            NSMutableArray *mySegments = [[NSMutableArray alloc] initWithCapacity:count];
            
            
            for (int i = 0; i<count; i++) {
                jsonData = [tArray objectAtIndex:i];
                 //NSString * desc = jsonData[@"vehicleMake"],"-", jsonData[@"vehicleMake"];
                
                [mySegments addObject:jsonData[@"vehicleMake"]];
                [mySegments addObject:jsonData[@"vehicleRegNo"]];
                
            }
            [self.carSegment removeAllSegments];
            self.carSegment = [self.carSegment initWithItems:mySegments];
            
            
            
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception: %@", e);
        [self alertStatus:@"Sign in Failed." :@"Error!" :0];
    }
}

Upvotes: 0

Views: 644

Answers (4)

nhgrif
nhgrif

Reputation: 62052

So, as KerrM's answer points out, this exception is happening because we're trying to access an index which doesn't exist in our array.

jsonData = [tArray objectAtIndex:1];

This is exactly what the bolded part of the exception you put in the question is saying.

So what should we do to fix it? Well, I don't find the other answers that offer solutions to be particularly that great. In all of them, we're still accessing a hard-coded array index. Sure, we're putting a check in to make sure it exists, but very, very rare should it be that you actually need to directly access an array index that you can hardcode.

Instead, there are these solutions:

  1. Any time we need to access the first object in an array, we can safely do so using the firstObject method, which will always return the object at index 0, unless the array is empty, in which case it will return nil. If you access an array's 0th index when it is empty, you still get the index out of bounds exception.
[myArray firstObject];
  1. Any time we need to access the last object in an array, we can safely do so using the lastObject method. This is the same as firstObject, except it looks at the last index of the array. No matter how many objects in the array, this will return the last one. And if it's an empty array, it will safely return nil.
[myArray lastObject];
  1. And finally, the scenario most applicable to your situation, when we need to loop over all the elements in an array, we should do so using some form of fast enumeration. The easiest to use and most common way of doing this which will work in most scenarios is simply to use a forin loop. There are several advantages to using a forin loop. For a start, they're faster than a regular for loop. But there are other advantages too. We're not accessing our array by index (which helps make the loop faster) and that means we can't have an index out of bounds exception. Finally though, it just looks a whole lot cleaner and more readable than a regular for loop.
for (NSDictionary *jsonData in tArray) {
    [mySegments addObject:jsonData[@"vehicleMake"]];
    [mySegments addObject:jsonData[@"vehicleRegNo"]];
}

In terms of answering the question of how come tArray only has one object in it, you'll have to look at the JSON data itself. In fact, you should have already done this. There's no guarantee that the JSON data is an array of dictionaries. Most JSON or XML data I see typically has a dictionary as the root object. But the point is that the method signature looks like this:

+ (id)JSONObjectWithData:(NSData *)data
                 options:(NSJSONReadingOptions)opt
                   error:(NSError **)error

The return type is id. When Apple released Swift and iOS 8, they also went through Objective-C and eliminated nearly all uses of id, replacing most with instancetype. They're moving away from the use of id. But this one remains. Why? Because it must. In this case, we're using id because we can't use instancetype. We're using id because the return value could be an NSArray, or it could be an NSDictionary. (It can probably also be an NSNumber or NSString object, perhaps.)

So we should definitely be clear on what the expected JSON looks like before we write the code to grab data out of it...

Upvotes: 1

johny kumar
johny kumar

Reputation: 1270

You have an array and you have 1 element in it. Now you are trying to extract 2nd element from it, [0] is first, and [1] is second.

Error-->> jsonData = [tArray objectAtIndex:i];

Check First wether it contains items are more than one

if([tArray count]== 1 && selectedSegment == 0){
   jsonData = [tArray objectAtIndex:0];
else if ([tArray count]> 1 && selectedSegment == 1) {
    //toggle the correct view to be visible
    jsonData = [tArray objectAtIndex:1];
    self.displayInsurer.text=jsonData[@"insurerName"];  
}

Upvotes: 0

Kanan Vora
Kanan Vora

Reputation: 222

You must first check the number of items in NSArray before accessing them:

if (tArray.count > count) {
        jsonData = [tArray objectAtIndex:count-1];
        self.displayInsurer.text=jsonData[@"insurerName"];
}

like this!!! It will never crash then...

All the best...

Upvotes: 0

KerrM
KerrM

Reputation: 5240

Your array only has 1 item (at index 0) and you're trying to access an item at index 1 here:

jsonData = [tArray objectAtIndex:1];

Upvotes: 3

Related Questions