I've been developing app, that makes asynchronous requests for JSON type of data. Lately, I found strange bug in my code and I can't tell why it is happening.
Ok to the code!
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error=nil;
result = [NSJSONSerialization JSONObjectWithData:retrievedData options:kNilOptions error:&error];
NSLog(#"Result %#",result);
NSLog(#"Retrieved data %#",retrievedData);
}
Result is NSDictionary, retrievedData is NSMutableData.
99% of the time, it works fine, connectionDidFinishLoading gets called, and my result is populated. However, in that 1% of the time retrieved data is filled with data, but my result is null.(as you can see on the picture. Could anyone help me please?
Edit: I get following error
Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Garbage at end.) UserInfo=0x753e5c0 {NSDebugDescription=Garbage at end.}
It would help if you take a look at the error first, to see if that gives any indication of what is wrong.
[edit]
Your error mentions the reason: Garbage at end..
The response from the webserver is not valid JSON, it contains invalid characters at the end of the output.
I had the same issue. First, look what is a data that wasn't parsed correctly - in my case I did
NSString *str = [[NSString alloc] initWithData:retrievedData encoding:NSUTF8StringEncoding];
In my case the reason was - if server sent a few socket.write()'s in a row - all data was received in one single chunk, like
{first:json}{second:json}..
of course this cannot be parsed as one single json, so I have to introduce delimiter, and split the received buffer into correct chunks.
This is a little late but nothing online worked for me until I did this:
NSString * dataInString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSRange range = [dataInString rangeOfString:#"}" options:NSBackwardsSearch];
if(range.location != NSNotFound)
dataInString = [dataInString substringWithRange:NSMakeRange(0,range.location+1)];
Always worked since then.
I had the same error, the problem was my server was attaching some extra lines to my json response, that would not appear when i will get the response in my browser. Using curl from terminal you can see the actual output.
The hack was to truncate the extra characters. with json either you have an array or dictionary. Depending on your json structure, you can use the code (as above answer) but look for the comment in line 2
NSString * str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSRange range = [str rangeOfString:#"}" options:NSBackwardsSearch]; // } for dictionary, ] for array
if(range.location != NSNotFound)
str = [str substringWithRange:NSMakeRange(0,range.location+1)];
once you get your string clean from garbage data, you can convert it to data again.
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
and now from this data you can get your array or dictionary.
id jsonObject = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions error:&error]; // i used id to be general, you can use array or dictionary structure or later cast this object to as per json
Related
In the reference for [NSString initWithData:encoding:] it says,-
"Returns nil if the initialization fails for some reason (for example if data does not represent valid data for encoding)"
I know the data and encoding are correct, because the data was returned by NSJSONSerialization dataWithJSONObject, so it must be valid and it must be UTF8
My question is:
What other reason could cause NSString initWithData to return nil?
I have researched this extensively and only found answers that suggest the data and/or encoding may be invalid, but I have checked and these are definitely correct.
My code is very similar to the following:
NSData* data = [NSJSONSerialization dataWithJSONObject:array
options:0
error:nil];
if (!data) return nil;
NSString* string = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
array must be valid because data is not nil
data must be valid & UTF8 because it was returned by NSJSONSerialization dataWithJSONObject
I cannot post my data because it is proprietary which should not matter for the reasons given
What other reason could cause NSString initWithData to return nil?
I have some problems with creating a NSString-representation (JSON-string) of a NSDictionary using NSJSONSerialization. I have used JSONKit before, but since its kind of deprecated in iOS9 (and crashes), I switched to NSJSONSerialization.
This is my code:
// `userSettings` will be of type NSMutableDictionary*
NSData* data= [NSJSONSerialization dataWithJSONObject:userSettings options:0 error:&error];
NSString* settingsString= [NSString stringWithUTF8String:data.bytes];
currentUser.settings= settingsString; // NSString* property
Now, from time to time, this code works, but then sometimes the settingsString will be nil. And when I inspect the data object in the debugger, the bytes property shows the JSON-String followed by some random garbage, like this:
1 = 0x00007ff1ba814000 "{\"residua
...
lculatePlanned\":\"0\",\"wizardUserId\":\"\"}UITextColor\x91UISystemColorName\x94UIBaselineAdjustment\x8cUISystemFont\x89NS.intval\x8eUIShadowOffset\x94UIAutoresizeSubviews\x8dUIContentMode\x85NSRGB\x8aUIFontName\x8bUITextLabel\x8eNSInlinedValue\x91UIDetailTextLabel\x99UIUserInteractionDisabled\x9dUITableCellBackgroundColorSet\x94UINibEncoderEmptyKey\x87NSWhite\x8cNSColorSpace\x8fUITextAlignment\xa3UINibAccessibilityConfigurationsKey\x92UIAutoresizingMask\x99UIProxiedObjectIdentifier\x87UIAlpha\x87UIWhite\x9aUIFontDescriptorAttributes\x8cUIFontTraits\x86NSSize\x95UIColorComponentCount\x91UIMinimumFontSize\x86UIText\x96UIMultipleTouchEnabled\x8dUIDestination\x94UIMi..."
^ start of garbage after end of dictionary
What am I doing wrong?
Do not use + stringWithUTF8String:, it relies on a A NULL-terminated C array of bytes and there is only a NULL terminator by chance and it may be well after the end of the charactery you expect.
Instead use:
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
Ex:
NSString *settingsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
EDIT: I did not do a very good job of explaining for the server works and my apologies for the same. So here are two things in which the server works ( expects data from clients)
1) Server has a size limitation for receiving image data. It expects images to be broken up in chunks of byte array
2) Server expects to receive these chunks of byte array through JSON.
So I am assuming this translates to the following on the client side
1) I need to break the image in parts
2) Create a Byte array of each part
3) Bind those byte array with JSON and send with server
Once received by the server, those are constructed as an Image by the server.
I am trying to achieve the above mentioned goal by the following approach (I keep the image file in NSData, then I create a Byte Buffer and keep chunks of the image file's NSData in that buffer. Post that I bind this buffer with JSON )
Following is the code for above approach:
-(void)dividepacketId:(int)pktId fileData:(NSData*)dt //dt contain the NSData of image file
{
Byte buffer[20480];
long long dtLength,from=0;
long long len=[dt length];
BOOL b=YES;
while (b)
{
int k=0,indexCont=0;
if(len>20480)
{
dtLength=20480;
}
else
{
dtLength=len;
b=NO;
}
[dt getBytes:buffer range:NSMakeRange(from,dtLength)];
NSData *imageData=nil;
imageData = [NSData dataWithBytes:buffer length:dtLength];
len=len-dtLength;
from=from+dtLength;
NSLog(#"sending buffer=%s legth of buffer=%lli len value=%lli",buffer,dtLength,len); //everything is fine till here
NSMutableDictionary *projectDictionary3 = [NSMutableDictionary dictionaryWithCapacity:1];
[projectDictionary3 setObject:#"2100" forKey:#"Action"];
[projectDictionary3 setObject:[NSString stringWithFormat:#"%i",pktId] forKey:#"PacketId"];
[projectDictionary3 setObject:#"101" forKey:#"FileAction"];
if(imageData!=nil)
[projectDictionary3 setObject: imageData forKey:#"FData"];//data
[projectDictionary3 setObject:[NSString stringWithFormat:#"%i",(int)dtLength] forKey:#"DataLength"];//data
NSError *jsonSerializationError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:projectDictionary3 options:NSJSONWritingPrettyPrinted error:&jsonSerializationError]; //"here crashed"
if(!jsonSerializationError)
{
NSString *serJSON = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"Serialized JSON: %#", serJSON);
}
else
{
NSLog(#"JSON Encoding Failed: %#", [jsonSerializationError localizedDescription]);
}
code to send over stream
[self sendDataToServer:jsonData];
} //while loop
}
Here is the challenge. If I send simple data (for example a string) through this code, it goes over to server successfully ( through socket). But when I try to break an actual jpeg image into parts and bind it in nsdictionary to make json, it crashes with the following error.
terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (NSConcreteData)'. Any help here would be highly appreciated.
EDIT: As explained by bobnoble, I understand the reason for the exception. In that case however, how do I accomplish sending over data to server
Deleting all but the key portions that need to be changed.
As I stated in the comments the data needs to be in a format JSON handles, raw bytes are not acceptable so one way is to encode the data with Base64. The receiver will also need to decode the Base64 string into data.
while (b) {
//get chunk from and dtLength
NSData *imageData = [dt subdataWithRange:NSMakeRange(from, dtLength)];
NSData *imageBase64Data = [imageData base64EncodedDataWithOptions:0];
NSString *imageBase64String = [[NSString alloc] initWithData:imageBase64Data encoding: NSUTF8StringEncoding];
// update len and from
NSLog(#"sending imageData =%#. dtLength =%i len =%lli", imageBase64String, dtLength, len);
// Create projectDictionary3 and add items
// Added image data)
if(imageData.length) {
[projectDictionary3 setObject: imageBase64String forKey:#"FData"];
}
[projectDictionary3 setObject:#(imageBase64String.length) forKey:#"DataLength"];
// serialize projectDictionary3 into JSON and sent to server];
}
From the NSJSONSerialization class reference:
An object that may be converted to JSON must have the following
properties:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
The second bullet does not include NSData, and is why the exception is being thrown.
Convert the image data to Base64 encoding, then put it in the dictionary as an NSString. Take a look at the NSData base64EncodedStringWithOptions method.
NSData* jsonData is the http response contains JSON data.
NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"jsonString: %#", jsonString);
I got the result:
{ "result": "\u8aaa" }
What is the proper way to encoding the data to the correct string, not unicode string like "\uxxxx"?
If you convert the JSON data
{ "result" : "\u8aaa" }
to a NSDictionary (e.g. using NSJSONSerialization) and print the dictionary
NSError *error;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSLog(#"%#", jsonDict);
then you will get the output
{
result = "\U8aaa";
}
The reason is that the description method of NSDictionary uses "\Unnnn" escape sequences
for all non-ASCII characters. But that is only for display in the console, the dictionary is correct!
If you print the value of the key
NSLog(#"%#", [jsonDict objectForKey:#"result"]);
then you will get the expected output
說
I don't quite understand what the problem is. AFNetworking has given you a valid JSON packet. If you want the above code to output the character instead of the \u… escape sequence, you should coax the server feeding you the result to change its output. But this shouldn't be necessary. What you most likely want to do next is run it through a JSON deserializer…
NSDictionary * data = [NSJSONSerialization JSONObjectWithData:jsonData …];
…and you should get the following dictionary back: #{#"result":#"說"}. Note that the result key holds a string with a single character, which I'm guessing is what you want.
BTW: In future, I suggest you copy-paste output into your question rather than transcribing it by hand. It'll avoid several needless rounds of corrections and confusion.
I'm having a hard time parsing the below JSON string on iOS 5.
{"States": [{"Name": "Arizona","Cities": [{"Name": "Phoenix"}]},{"Name": "California","Cities": [{"Name": "Orange County"},{"Name": "Riverside"},{"Name": "San Diego"},{"Name": "San Francisco"}]},{"Name": "Nevada","Cities": [{"Name": "Las Vegas"}]}]}
And here's my code:
- (void) parseJson {
NSError *jsonError = nil;
NSData *jsonData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Locations-JSON" ofType:#"rtf"]];
if (jsonData) {
NSDictionary *jsonObjects = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&jsonError];
if (jsonError) {
NSLog(#"JSON Error: %#", [jsonError localizedDescription]);
return;
}
NSLog(#"%#", jsonObjects);
}
}
I keep getting this error:
JSON Error: The operation couldn’t be completed. (Cocoa error 3840.)
I'd appreciate some help on this because I clearly and incapable of fixing this.
One thing that strikes me as incorrect is this:
[[NSBundle mainBundle] pathForResource:#"Locations-JSON" ofType:#"rtf"]
Your data is an RTF file?? It should be a txt file (or any other sort of plain text file). RTF files usually contain text formatting data, like this:
{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\margl1440\margr1440\vieww10800\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural
\f0\fs24 \cf0 \{"States": [\{"Name": "Arizona","Cities": [\{"Name": "Phoenix"\}]\},\{"Name": "California","Cities": [\{"Name": "Orange County"\},\{"Name": "Riverside"\},\{"Name": "San Diego"\},\{"Name": "San Francisco"\}]\},\{"Name": "Nevada","Cities": [\{"Name": "Las Vegas"\}]\}]\}}
When I read that in as a data and try to parse it as JSON, I get the 3840 error you're seeing. That error's description says:
The data couldn’t be read because it has been corrupted. (No string key for value in object around character 2.)
So what it looks like to me is that you don't actually have JSON. You have RTF data.
I had hit a similar problem. My JSON parser works intermittently when I download the JSON data from a server. Did you get your JSON data from this function?
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
The NSData returned from this function could be partial data. You need to appendData to an instance variable with type: NSMutableData. Then you process your JSON in another function as follows:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
Reading this article for the details. It works for me
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html
I was able to troubleshoot my JSON 3840 error by converting the NSData object to an NSString:
NSError *error;
NSObject *object = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
if (object == nil) {
NSString *serverResponse = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
NSLog(#"\n\nError:\n%#\n\nServer Response:\n%#\n\nCrash:", error.description, serverResponse);
[NSException raise:#"Invalid Data" format:#"Unable to process web server response."];
}
If you arrived here because of the JSON and not because of the RTF , please check out this answer :
IOS JSON Deserialization failure - STIG/NSJSONSerializer