Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSJSONSerialization Fails to Parse Valid JSON - "Garbage at End"

My iOS program is receiving JSON data and trying to parse it but always fails for some reason I cannot determine. Multiple threads are calling this function almost at the same time. This only started happening once I switched to using GCDAsyncSocket, strangely. Here is the relevant code for receiving and parsing data:

// Called whenever I want my program to receive null-terminated data from the server:
[socket readDataToData:[NSData dataWithBytes:"\0" length:1] withTimeout:10 tag:0];

- (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag{ // a GCDAsyncSocket delegate method
    [self didReceiveNetworkData:data];
}

- (void)didReceiveNetworkData: (NSData*) data{
    if (TESTING) NSLog(@"Received network data of length %lu===\n%@\n===", (unsigned long) data.length, [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding]);
    NSError* error;
    NSDictionary* json = [NSJSONSerialization
                          JSONObjectWithData: data
                          options:kNilOptions
                          error:&error];
    if (!json){
        NSLog(@"Got an error parsing received JSON data: %@", error);
        return;
    }
    // Then I handle the dictionary (omitted code)…
}

The log and breakpoint debugger say that this is the data received:

{
"responseType": -1
}

To be exact, its bytes are "{\n\t\"responseType\":\t-1\n}\0". And I get this "garbage at end" error once the JSONObjectWithData function runs:

Got an error parsing received JSON data: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Garbage at end.) UserInfo=0x10b203800 {NSDebugDescription=Garbage at end.}

According to online JSON testers and my own simple test in a separate project, this is valid JSON. So why am I getting an error? I thought that maybe it was complaining about extra bytes after the null terminator, so I tried trimming it by making an NSString out of it then turning that back into NSData, but that did not help:

NSDictionary* json = [NSJSONSerialization
                      JSONObjectWithData: [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] dataUsingEncoding: NSUTF8StringEncoding]
                      options:kNilOptions
                      error:&error];

Because using GCDAsyncSocket is what started this problem, I'm wondering if it has to do with multithreading, but I really cannot think of any reason why that would matter. Any ideas?

P.S. I'm using GCDAsyncSocket because I don't know how to deal with low-level sockets in Objective-C, so my original code had tons of weird bugs.

like image 721
sudo Avatar asked Apr 27 '14 05:04

sudo


2 Answers

Strangely, this is what it took! At the start of the didReceiveNetworkData function:

//Yes, I know this wastefully allocates RAM, so I'll fix it later.
NSString* string = [[[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] stringByReplacingOccurrencesOfString:@"\t" withString:@""] stringByReplacingOccurrencesOfString:@"\0" withString:@""];
data = [string dataUsingEncoding:NSUTF8StringEncoding];

I removed all '\t' (before entries) and '\0' (at the end of string) characters. Removing just '\0' xor '\t' didn't work; it had to be both. Unless I'm missing something, it seems very strange that NSJSONSerialization cares both about tab characters and trailing null characters!

By the way, cJSON encoded this, so that means that cJSON and NSJSONSerialization do not strictly agree. The online JSON tester agrees with the way cJSON encoded it too.

I might be wrong anyway because this was working before without GCDAsyncSocket (though I still had to remove null characters, as I now recall). Or maybe my old solution was somehow removing the tab characters.

like image 108
sudo Avatar answered Oct 22 '22 14:10

sudo


Update: You are probably having a problem somehow with the escape characters. I am no JSON guru myself, but I remember that I once had a JSON string added into an dictionary and then created a JSON from that dictionary. The JSON string itself became escapes, so it would stay a string during parsing. My feeling is you need to remove the escape characters.

Maybe you have some race condition and are writing to the data while the JSONParsing is ongoing. Make sure you are passing NSData and not NSMutableData to the method that does the serialization to make sure. Remember that NSJSONSerialization is not a streaming JSON parser. It takes an immutable JSON object and serializes that into a Foundation object. If you are adding bytes to the NSData, NSJSONSerialization will see at some point stream of bytes that at that point is not valid JSON (yet).

like image 29
Joride Avatar answered Oct 22 '22 12:10

Joride