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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With