JSON data has "bad" characters that causes NSJSONSerialization to die - objective-c

I am using the ATV version of TVH Client - if you haven't looked at this it's worth looking at TVH to glimpse madness in the face. It has a JSON API that sends back data, including the electronic program guide. Sometimes the channels put accented characters in their data. Here is an example, this is the result from Postman, note the ? char in the description:
{
"eventId": 14277,
"episodeId": 14278,
"channelName": "49.3 CometTV",
"channelUuid": "02fe96403d58d53d71fde60649bf2b9a",
"channelNumber": "49.3",
"start": 1480266000,
"stop": 1480273200,
"title": "The Brain That Wouldn't Die",
"description": "Dr. Bill Cortner and his fianc�e, Jan Compton , are driving to his lab when they get into a horrible car accident. Compton is decapitated. But Cortner is not fazed by this seemingly insurmountable hurdle. His expertise is in transplants, and he is excited to perform the first head transplant. Keeping Compton's head alive in his lab, Cortner plans the groundbreaking yet unorthodox surgery. First, however, he needs a body."
},
If this data is fed into NSJSONSerialization, it returns an error. So to avoid this, the data is first fed into this function:
+ (NSDictionary*)convertFromJsonToObjectFixUtf8:(NSData*)responseData error:(__autoreleasing NSError**)error {
NSMutableData *FileData = [NSMutableData dataWithLength:[responseData length]];
for (int i = 0; i < [responseData length]; ++i) {
char *a = &((char*)[responseData bytes])[i];
if ( (int)*a >0 && (int)*a < 0x20 ) {
((char*)[FileData mutableBytes])[i] = 0x20;
} else {
((char*)[FileData mutableBytes])[i] = ((char*)[responseData bytes])[i];
}
}
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:FileData //1
options:kNilOptions
error:error];
if( *error ) {
NSLog(#"[JSON Error (2nd)] output - %#", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
NSDictionary *userInfo = #{ NSLocalizedDescriptionKey:[NSString stringWithFormat:NSLocalizedString(#"Tvheadend returned malformed JSON - check your Tvheadend's Character Set for each mux and choose the correct one!", nil)] };
*error = [[NSError alloc] initWithDomain:#"Not ready" code:NSURLErrorBadServerResponse userInfo:userInfo];
return nil;
}
return json;
}
This cleans up the case when there is a control character in the data, but not an accent like the case above. When I feed in that data I get the "Tvheadend returned malformed JSON" error.
One problem is that the user can change the character set among a limited number of selections, and the server does not tell the client what it is. So one channel might use UTF8 and another ISO-8891-1, and there is no way to know which to use on the client side.
So: can anyone offer a suggestion on how to process this data so we feed clean strings into NSJSONSerialization?

I still do not know the root cause of the problem I am seeing - the server is sending not only high-bit characters like the ones I noted above, but I also found that it contained control characters too! Looking over other threads it appears I am not the only one seeing this problem, so hopefully others will find this useful...
The basic trick is to convert the original data from the server to a string using UTF8. If there are any of these "bad" chars in it, the conversion will fail. So you check if the resulting string is empty, and try another charset. Eventually you'll get data back. Now you take that string and strip out any control chars. Now you take that result, which is now UTF8 "clean", and convert it back to UTF8 NSData. That will pass through the JSON conversion without error. Phew!
Here is the solution I finally used:
// ... the original data from the URL is in responseData
NSString *str = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if ( str == nil ) {
str = [[NSString alloc] initWithData:responseData encoding:NSISOLatin1StringEncoding];
}
if ( str == nil ) {
str = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
}
NSCharacterSet *controls = [NSCharacterSet controlCharacterSet];
NSString *stripped = [[str componentsSeparatedByCharactersInSet:controls] componentsJoinedByString:#""];
NSData *data = [stripped dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
I hope someone finds this useful!

Related

initWithBase64EncodedString return nil

My resultString is 'PHNhbWxwOlJlc3BvbnNlIH...c3BvbnNlPgoK' and when i am decoding it shows me decodedData as nil.
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:resultString options:0];
I also tried this string with https://www.base64decode.org/ ,
it successfully shows results.
What wrong here in decoding ?
Probably you have some invalid characters in your string, like padding new lines. Try to pass NSDataBase64DecodingIgnoreUnknownCharacters option instead of 0.
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:resultString options:NSDataBase64DecodingIgnoreUnknownCharacters];
Almost certainly your string is not valid Base64, but that it is "close enough" that base64decode.org accepts it. The most likely cause is that you've dropped a trailing =. base64decode.org is tolerant of that, and just quietly throws away what it can't decode (the last byte in that case). NSData is not tolerant of that, because it's not valid Base64.
base64decode.org is also tolerant of random non-base64 characters in the string and just throws them away. NSData is not (again, sine it's invalid).
Try this! Simple solution :) Must need Foundation.framework. By default initWithBase64EncodedString method returns nil when the input is not recognized as valid Base-64. Please check your string is a valid Base-64 type or not!
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:#"eyJuYW1lIjoidmlnbmVzaCJ9" options:0];
NSError *dataError;
NSDictionary* responseObject = [NSJSONSerialization JSONObjectWithData:decodedData
options:kNilOptions
error:&dataError];
if(dataError == nil) {
NSLog(#"Result %#",responseObject);
}

Mailcore: Inexpensive way to get message summary

I know I can get a plain text version of a message using plainTextRenderingOperationWithMessage: but it's very time consuming. I'm only trying to put together a short message summary. Is there a way to get this from the MCOIMAPMessage/MCOAbstractPart without using plainTextRenderingOperationWithMessages:?
I've got a semi-working solution with the following code but it still seems slow (about 2 seconds for the operation to complete) considering all the data should be contained in MCOIMAPMessage which is fully downloaded. Is this as fast as it gets or am I missing something?
MCOIMAPMessage *m = [NSKeyedUnarchiver unarchiveObjectWithData:self.email.mcomessage];
MCOIMAPFetchContentOperation * op =
[session fetchMessageAttachmentByUIDOperationWithFolder:folder
uid:[m uid]
partID:#"1"
encoding:MCOEncoding8Bit];
[op start:^(NSError * error, NSData * partData) {
NSString *string = [[NSString alloc] initWithData:partData encoding:NSUTF8StringEncoding];
string = [string mco_strippedWhitespace];
if (string.length > 200) string = [string substringToIndex:200];
NSLog(#"String: %#", string);
}];

Web Service JSON nsdictionary unicode string [duplicate]

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.

Try to create NSString from bytes. It always returns an empty string

I am trying to create a string from bytes a received via network. The NSString I het is always an empty one.
if (stringLength > 0) {
NSData *bytes = [[NSData alloc] initWithBytes:data+1 length:stringLength];
result = [[NSString alloc] initWithData:bytes encoding:NSUTF8StringEncoding];
//result = [[NSString alloc] initWithBytes:data+1 length:stringLength encoding:NSASCIIStringEncoding];
}
As I said I get an empty NSString. The string is a base64 encoded value so it should be valid utf-8 since it only contains ascii symbols.
Maybe your raw bytes aren't UTF-8 after all. Or maybe, because you're passing data+1 instead of data itself, you're causing the encoding attempt to fail because the process thinks there's an improper UTF-8 encoding sequence. (Forgive me if I'm being presumptuous, but you have to take everything into account.) In any case, you're relying heavily on your assumption, and that's a trap we all fall into now and then.
Here's a strategy for you. If your attempt at creating an NSString instance using NSUTF8StringEncoding returns nil, then try using NSISOLatin1StringEncoding. And if that returns nil, then try using NSMacOSRomanStringEncoding.
Even if, after all of that, you get a string that's not quite right, it's still better than a nil string in that it could help to show you if there's some other area in which you've made a mistake.
Good luck to you in your endeavors.
Check if it's really base64 encoded..
CFErrorRef error = NULL;
CFDataRef decodedData;
SecTransformRef decoder;
decoder = SecDecodeTransformCreate(kSecBase64Encoding, &error);
if (error) {
CFShow(error);
exit(-1);
}
SecTransformSetAttribute(decoder,
kSecTransformInputAttributeName,
(CFDataRef *)bytes,
&error);
if (error) {
CFShow(error);
exit(-1);
}
decodedData = SecTransformExecute(decoder, &error);
if (error) {
CFShow(error);
exit(-1);
}

How to parse a JSON having no quotes with its KEY string?

I want to parse the json output resulting from the following url in SBJSON framework for iOS
http://maps.google.com/maps?q=school&mrt=yp&sll=13.006389,80.2575&output=json
while(1);{title:"school - Google Maps",url:"/maps?q=school\x26mrt=yp\x26sll=13.006389,80.2575\x26ie=UTF8\x26hq=school\x26hnear=",urlViewport:false,ei:"RCu3T4eeMqSiiAe7k-yZDQ",form:{selected:"q",q:{q:"school",mrt:"yp",what:"school",near:""},d:{saddr:"",daddr:"",dfaddr:""},geocode:""},
I am using http://www.bodurov.com/JsonFormatter/ to read it online.
In ASIHttpRequest response method I removed while(1); from the response
NSString *responseString = [[request resonseString]substringFromIndex:9]; //to remove while(1)
SBJSONParser * parser = [[SBJSONParser alloc]init];
NSDictionary *jsonDict = (NSDictionary*)[parser objectFromString:responseString];
NSLog(#"%#",jsonDict) // prints null
// [responseString JSONValue] also reports error
I guess JSON key without double quotes is causing problem.
Instead of {
"title": "hospital - Google Maps",
"urlViewport": false,
}, we get {
title: "hospital - Google Maps",
"urlViewport": false
}
Please help me to parse this complex JSON structure returned from Google.
This worked better for my case because my values contained times which caused the regular expression in the above answer to match incorrectly.
json = [json stringByReplacingOccurrencesOfString: #"(\\w*[A-Za-z]\\w*)\\s*:"
withString: #"\"$1\":"
options: NSRegularExpressionSearch
range: NSMakeRange(0, json.length)];
You need to add the missing quotes to the keys, so try this:
responseString = [responseString stringByReplacingOccurrencesOfString:#"(\\w+)\\s*:"
withString:#"\"$1\":"
options:NSRegularExpressionSearch
range:NSMakeRange(0, [responseString length])];
This should work well with the given JSON string.