Unrecognized selector error when indexing into a dictionary of arrays - objective-c

I have a dictionary of arrays that is causing __NSCFDictionary objectAtIndex: errors.
Can someone tell me why? The dictionary clearly has at least 1 array at the time of the error.
NSError *error;
responseString = [[NSString alloc] initWithData:self.responseData2 encoding:NSUTF8StringEncoding];
/* response string contains this:
{"words":
{
"word": {"rowsreturned":0,"id":"-1","date":"","word":"","term":"","definition":"","updated_on":""}
},
"status":"",
"rowsreturned":""
}
*/
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:self.responseData2 options:kNilOptions error:&error];
NSArray *todaysWord = [[json objectForKey:#"words"] objectForKey:#"word"];
//error here -[__NSCFDictionary objectAtIndex:]: unrecognized selector sent to instance
NSDictionary *word = [todaysWord objectAtIndex:0];

In your case [[json objectForKey:#"words"] objectForKey:#"word"]; is returning a dictionary and not an array. Try doing the following,
id wordParam = [[json objectForKey:#"words"] objectForKey:#"word"];
if ([wordParam isKindOfClass:[NSArray class]]) {
NSDictionary *word = [(NSArray *)wordParam objectAtIndex:0];
} else if ([wordParam isKindOfClass:[NSDictionary class]]) {
NSDictionary *word = (NSDictionary *)wordParam;
} else {
NSLog(#"error. %# is not an array or dictionary", wordParam);
}
Your response string also shows that value for word is,
{"rowsreturned":0,"id":"-1","date":"","word":"","term":"","definition":"","updated_on":""}
which is a dictionary with key value pairs as rowsreturned:0, id:-1 etc..

Related

How to parse JSON string in objective C? [duplicate]

This question already has answers here:
How do I parse JSON with Objective-C?
(5 answers)
Closed 6 years ago.
I have the following json:
NSString *s = #"{"temperature": -260.65, "humidity": 54.05, "time": "2016-03-14T09:46:48Z", "egg": 1, "id": 6950, "no2": 0.0}";
I need to extract data from json to strings
NSString temperature
NSString humidity
NSString no2
How to do it properly?
you can use NSJSONSerialization class. first you need to convert your string to an NSData object after that you will get the JSON data. have a look on the code
// json s string for NSDictionary object
NSString *s = #"{\"temperature\": -260.65, \"humidity\": 54.05, \"time\": \"2016-03-14T09:46:48Z\", \"egg\": 1, \"id\": 6950, \"no2\": 0.0}";
// comment above and uncomment below line, json s string for NSArray object
// NSString *s = #"[{\"ID\":{\"Content\":268,\"type\":\"text\"},\"ContractTemplateID\":{\"Content\":65,\"type\":\"text\"}}]";
NSData *jsonData = [s dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
// Note that JSONObjectWithData will return either an NSDictionary or an NSArray, depending whether your JSON string represents an a dictionary or an array.
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (error) {
NSLog(#"Error parsing JSON: %#", error);
}
else
{
if ([jsonObject isKindOfClass:[NSArray class]])
{
NSLog(#"it is an array!");
NSArray *jsonArray = (NSArray *)jsonObject;
NSLog(#"jsonArray - %#",jsonArray);
}
else {
NSLog(#"it is a dictionary");
NSDictionary *jsonDictionary = (NSDictionary *)jsonObject;
NSLog(#"jsonDictionary - %#",jsonDictionary);
}
}
After making the NSURL Request in the completion block u can do this:-
NSMutableDictionary *s = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSString *temperature =[s objectForKey:#"temperature"];
NSString *humidity = [s objectForKey:#"humidity"];

JSON Parsing Error (blank response)

Here's my code, when I run it, I get: "2014-10-26 19:02:09.153 App[27372:1281902] Price: (
)".
I was wondering why, no errors are being passed through and it honestly confuses me.
(I'm getting a blank response for "omc_usd_price")
#try
{
NSURL *url = [NSURL URLWithString:#"https://omnicha.in/api?method=getinfo"];
NSData *data=[NSData dataWithContentsOfURL:url];
NSError *error;
NSMutableDictionary *JSONStuff= [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error: &error];
NSLog(#"%#",JSONStuff);
NSMutableArray * OMCArray = [[NSMutableArray alloc]init];
NSArray * responseArr = JSONStuff[#"omc_usd_price"];
for(NSDictionary * dict in responseArr)
{
[OMCArray addObject:[dict valueForKey:#"omc_usd_price"]];
}
NSLog(#"Price: %# test", OMCArray); // Here you get the Referance data
}
#catch (NSException *exception) {
NSLog(#"%#", exception);
}
#finally {
}
EDIT:
Tried this, I don't think I did this right either!
NSMutableArray * OMCArray = [[NSMutableArray alloc]init];
NSMutableArray * OMCArray2 = [[NSMutableArray alloc]init];
NSArray * responseArr = JSONStuff[#"response"];
NSArray * responseArr2 = JSONStuff[#"omc_usd_price"];
for(NSDictionary * dict in responseArr)
{
[OMCArray addObject:[dict valueForKey:#"response"]];
for(NSDictionary * dict2 in responseArr2)
{
[OMCArray addObject:[dict2 valueForKey:#"omc_usd_price"]];
}
}
NSLog(#"Price: %# test", OMCArray2); // Here you get the Referance data
Here is the JSONStuff dictionary:
{
"error":false,
"response":{
"block_count":96136,
"difficulty":12.18364177,
"netmhps":234.652099,
"seconds_since_block":694,
"avg_block_time":196.533,
"total_mined_omc":6426691.6,
"omc_btc_price":7.0e-6,
"omc_usd_price":0.0025,
"market_cap":15833.5909
}
}
As you can see, there is no entry in the dictionary named "omc_usd_price". There is a entry by that name in the dictionary name "response", but you didn't ask for that.
If you want to get omc_usd_price, as mentioned on your code, then you need to parse 2 dictionaries.
You must first parse the dictionary with the key response, and this will give you a new dictionary.
Then in this new dictionary you must parse/look for the key omc_usd_price.
Also, you're not getting an array back, but a double or float or even a string.

Strange NSData Output

I have some unexpected results coming from the following code:
- (NSData *)postDataWithDict:(NSDictionary *)postDict
{
// Assume key is urlValid
NSUInteger postCount = [postDict count];
NSMutableArray *buildArray = [[NSMutableArray alloc] initWithCapacity:postCount];
for (NSString *key in postDict) {
//post data is key=value&key=value&key=value...
// start with key
NSMutableString *arrayLine = [NSMutableString stringWithString:key];
[arrayLine appendString:#"="];
// analyze and then append value
id postValue = [postDict objectForKey:key];
if ([postValue isKindOfClass:[NSNumber class]]) {
NSString *valueString = [NSString stringWithFormat:#"%#",postValue];
[arrayLine appendString:valueString];
}
else if ([postValue isKindOfClass:[NSString class]]) {
NSString *urlEncodedString = [self urlEncodeValue:postValue];
[arrayLine appendString:urlEncodedString];
}
else {
NSLog(#"postKey: %#, postValue class:%#", key, [postValue class]);
NSError *jsonError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postValue
options:0
error:&jsonError];
if (jsonError != nil) {
NSLog(#"JSON serialization failed: %# - %#", [jsonError localizedDescription], [jsonError userInfo]);
NSLog(#"value: %#", postValue);
}
else {
// need to urlencode
NSString *stringifyJSON = [NSString stringWithUTF8String:[jsonData bytes]];
NSString *urlJSONstring = [self urlEncodeValue:stringifyJSON];
[arrayLine appendString:urlJSONstring];
}
}
[buildArray addObject:arrayLine];
}
NSString *postString = [buildArray componentsJoinedByString:#"&"];
NSData *postData = [postString dataUsingEncoding:NSUTF8StringEncoding];
//testing
NSLog(#"Post Dict: %#", postDict);
NSLog(#"Post Array: %#", buildArray);
NSLog(#"Post String: %#", postString);
NSLog(#"Post Data: %#", [NSString stringWithUTF8String:[postData bytes]]);
return postData;
}
My //testing log results:
Post Dict: {
authenticationString = b3210c0bc6d2c47f4c2f7eeea12e063d;
dataMode = updateSingle;
dateCreated = "374300293.81108";
dateModified = "374609294.313093";
dateSynced = "374610683.588062";
entityName = CommodityTypes;
myName = 21;
sortKey = 21;
username = iPhoneAdamek;
usernameString = iPhoneAdamek;
uuidKey = "53403EAE-DD4F-4226-A979-316EF7F43991";
}
Post Dict looks good. Just what I wanted.
2012-11-14 13:31:23.640 FoodyU[11393:907] Post Array: (
"myName=21",
"dataMode=updateSingle",
"dateSynced=374610683.588062",
"uuidKey=53403EAE-DD4F-4226-A979-316EF7F43991",
"sortKey=21",
"dateModified=374609294.313093",
"entityName=CommodityTypes",
"dateCreated=374300293.81108",
"authenticationString=b3210c0bc6d2c47f4c2f7eeea12e063d",
"usernameString=iPhoneAdamek",
"username=iPhoneAdamek"
)
Post Array looks good. Strings are all set to be concatenated for a HTTP POST string.
2012-11-14 13:31:23.641 FoodyU[11393:907] Post String: myName=21&dataMode=updateSingle&dateSynced=374610683.588062&uuidKey=53403EAE-DD4F-4226-A979-316EF7F43991&sortKey=21&dateModified=374609294.313093&entityName=CommodityTypes&dateCreated=374300293.81108&authenticationString=b3210c0bc6d2c47f4c2f7eeea12e063d&usernameString=iPhoneAdamek&username=iPhoneAdamek
Post String looks good. I'm ready to convert it to data to use in [NSMutableURLRequest setHTTPBody:postData].
2012-11-14 13:31:23.643 FoodyU[11393:907] Post Data: myName=21&dataMode=updateSingle&dateSynced=374610683.588062&uuidKey=53403EAE-DD4F-4226-A979-316EF7F43991&sortKey=21&dateModified=374609294.313093&entityName=CommodityTypes&dateCreated=374300293.81108&authenticationString=b3210c0bc6d2c47f4c2f7eeea12e063d&usernameString=iPhoneAdamek&username=iPhoneAdamekoneAdamek;
usernameString = iPhoneAdamek;
uuidKey = "53403EAE-DD4F-4226-A
WTF??? How did &username=iPhoneAdamek become &username=iPhoneAdamekoneAdamek;
usernameString = iPhoneAdamek;
uuidKey = "53403EAE-DD4F-4226-A?
I'm fairly new to Cocoa. Is there something wrong with:
NSData *postData = [postString dataUsingEncoding:NSUTF8StringEncoding];
or
NSLog(#"Post Data: %#", [NSString stringWithUTF8String:[postData bytes]]);
You shouldn't be using NSLog of NSData as,
NSLog(#"Post Data: %#", [NSString stringWithUTF8String:[postData bytes]]);
Instead use it as,
NSLog(#"Post Data: %#", [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding]);
[NSString stringWithUTF8String:[postData bytes]] always returns unexpected results.
As per documentation for bytes,
bytes: Returns a pointer to the receiver’s contents.
And as per Apple documentation for stringWithUTF8String,
stringWithUTF8String:
Returns a string created by copying the data from a given C array of UTF8-encoded bytes.
Parameters: bytes - A NULL-terminated C array of bytes in UTF8 encoding.
So when you are using [postData bytes], it is not NULL-terminated and hence when you are using with stringWithUTF8String returns the data written in memory till it encounters a NULL-termination.

NSJSONSerialization not creating mutable containers

Here's the code:
NSError *parseError;
NSMutableArray *listOfObjects = [NSJSONSerialization JSONObjectWithData:[#"[]" dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&parseError];
NSLog(#"Is mutable? %li", [listOfObjects isKindOfClass:[NSMutableArray class]]);
listOfObjects = [NSJSONSerialization JSONObjectWithData:[#"[[],{}]" dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&parseError];
NSLog(#"Is mutable? %li", [listOfObjects isKindOfClass:[NSMutableArray class]]);
As you can see, I'm calling exactly the same method for parsing the JSON both times, one with an empty list in the JSON, and then a list with an object inside. Here's the result:
Is mutable? 0
Is mutable? 1
The problem is that the NSJSONSerialization doesn't seem to follow the option to create mutable containers for empty lists. Seems like a bug to me, but maybe I just misunderstanding things.
Any ideas?
This works just as expected:
NSString *s = #"{ \"objs\": [ \"a\", \"b\" ] }";
NSData *d = [NSData dataWithBytes:[s UTF8String] length:[s length]];
id dict = [NSJSONSerialization JSONObjectWithData:d options:NSJSONReadingMutableContainers error:NULL];
NSLog(#"%#", dict);
[[dict objectForKey:#"objs"] addObject:#"c"];
NSLog(#"%#", dict);
NSLog(#"%#", [[dict objectForKey:#"objs"] class]);
Here's the console output:
2012-03-28 13:49:46.224 ExampleRunner[42526:707] {
objs = (
a,
b
);
}
2012-03-28 13:49:46.225 ExampleRunner[42526:707] {
objs = (
a,
b,
c
);
}
2012-03-28 13:49:46.225 ExampleRunner[42526:707] __NSArrayM
EDIT
Note that if we append the following line to the code above...
NSLog(#"%#", [[dict objectForKey:#"objs"] superclass]);
...we get the following output on the console:
2012-03-28 18:09:53.770 ExampleRunner[42830:707] NSMutableArray
...just in case it wasn't clear that __NSArrayM is a private subclass of NSMutableArray, thus proving that the OP's code did indeed work as expected (except for his NSLog statement).
EDIT
Oh, and by the way, the following line of code...
NSLog(#"%d", [[dict objectForKey:#"objs"] isKindOfClass:[NSMutableArray class]]);
...results in the following console output:
2012-03-28 18:19:19.721 ExampleRunner[42886:707] 1
EDIT (responding to changed question)
Interesting...looks like a bug. Given the following code:
NSData *dictData2 = [#"{ \"foo\": \"bar\" }" dataUsingEncoding:NSUTF8StringEncoding];
id dict2 = [NSJSONSerialization JSONObjectWithData:dictData2 options:NSJSONReadingMutableContainers error:NULL];
NSLog(#"%#", [dict2 class]);
NSLog(#"%#", [dict2 superclass]);
NSLog(#"%d", [dict2 isKindOfClass:[NSMutableDictionary class]]);
// This works...
[dict2 setObject:#"quux" forKey:#"baz"];
NSLog(#"%#", dict2);
NSData *dictData = [#"{}" dataUsingEncoding:NSUTF8StringEncoding];
id emptyDict = [NSJSONSerialization JSONObjectWithData:dictData options:NSJSONReadingMutableContainers error:NULL];
NSLog(#"%#", [emptyDict class]);
NSLog(#"%#", [emptyDict superclass]);
NSLog(#"%d", [emptyDict isKindOfClass:[NSMutableDictionary class]]);
//...but this fails:
[emptyDict setObject:#"quux" forKey:#"baz"];
NSLog(#"%#", emptyDict);
Here's the console output:
2012-03-29 09:40:52.781 ExampleRunner[43816:707] NSMutableDictionary
2012-03-29 09:40:52.782 ExampleRunner[43816:707] 1
2012-03-29 09:40:52.782 ExampleRunner[43816:707] __NSCFDictionary
2012-03-29 09:40:52.782 ExampleRunner[43816:707] NSMutableDictionary
2012-03-29 09:40:52.783 ExampleRunner[43816:707] 1
2012-03-29 09:40:52.783 ExampleRunner[43816:707] {
baz = quux;
foo = bar;
}
2012-03-29 09:40:52.784 ExampleRunner[43816:707] __NSCFDictionary
2012-03-29 09:40:52.784 ExampleRunner[43816:707] NSMutableDictionary
2012-03-29 09:40:52.784 ExampleRunner[43816:707] 1
2012-03-29 09:40:52.785 ExampleRunner[43816:707] NSException: -[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
So empty arrays and dictionaries created this way don't seem to behave as expected.
Here's my workaround for this problem:
#import "NSJSONSerialization+MutableBugFix.h"
#implementation NSJSONSerialization (NSJSONSerialization_MutableBugFix)
+ (id)JSONObjectWithDataFixed:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error {
id object = [NSJSONSerialization JSONObjectWithData:data options:opt error:error];
if (opt & NSJSONReadingMutableContainers) {
return [self JSONMutableFixObject:object];
}
return object;
}
+ (id)JSONMutableFixObject:(id)object {
if ([object isKindOfClass:[NSDictionary class]]) {
// NSJSONSerialization creates an immutable container if it's empty (boo!!)
if ([object count] == 0) {
object = [object mutableCopy];
}
for (NSString *key in [object allKeys]) {
[object setObject:[self JSONMutableFixObject:[object objectForKey:key]] forKey:key];
}
} else if ([object isKindOfClass:[NSArray class]]) {
// NSJSONSerialization creates an immutable container if it's empty (boo!!)
if (![object count] == 0) {
object = [object mutableCopy];
}
for (NSUInteger i = 0; i < [object count]; ++i) {
[object replaceObjectAtIndex:i withObject:[self JSONMutableFixObject:[object objectAtIndex:i]]];
}
}
return object;
}
#end
So I call:
NSDictionary *object = [NSJSONSerialization JSONObjectWithDataFixed:jsonData options:NSJSONReadingMutableContainers error:&err];
Instead of the usual:
NSDictionary *object = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
Others also take this for a bug, see
https://github.com/couchbaselabs/TouchDB-iOS/issues/44 or
https://github.com/johnlabarge/jlbiosutils/blob/master/jlbiosutils/DynamicProperties.m, for example.
In the latter case you can see complete workaround also for empty dictionaries (see DynamicGetter(...) method).
Here is what I do:
BOOL needsWorkaround = YES;
if (needsWorkaround)
{
NSMutableDictionary* appState2 =
(__bridge_transfer NSMutableDictionary*)
CFPropertyListCreateDeepCopy (
kCFAllocatorDefault, (__bridge void*)appState,
kCFPropertyListMutableContainersAndLeaves
);
appState = appState2;
}

AFHttpClient invalid tokens in dictionary when using AFFormURLParameterEncoding

I've looked through SO and Google and haven't found a similiar issue to this. I feel like the answer is staring me in the face and I just need another set of eyes.
I'm using AFNetworking to connect to the Stripe.com API. Specifically I'm using AFHTTPClient postPath to send data to an endpoint, charges. Stripe requires the request to be encoded as application/x-www-form-urlencoded so I can't use JSON encoding.
The problem I'm running into is that I have a Charge object and a Card object. Card is a property on Charge and I convert both Charge and Card to NSDictionary's (Card is an dictionary inside of the Charge dictionary) and then pass them in as the parameters on the request like so:
NSDictionary *parameters = [ChargeRequest convertToDictionary:request];
[[StripeAPIClient sharedClient] postPath:#"charges" parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Response: %#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error localizedDescription]);
NSLog(#"Response: %#", operation.responseString);
}];
When I do this, with AFHttpClient's parameterEncoding property set to AFFormURLParameterEncoding, Stripe returns this error:
"error": {
"message": "Invalid token id: {\n \"exp_month\" = 10;\n \"exp_year\" = 2016;\n number = 4242111111111111;\n}",
"type": "invalid_request_error"
}
The values in the error are specifically the key/values on the Card object after converting it. Here is the code I use for the conversion:
return [[NSDictionary alloc] initWithObjectsAndKeys:request.number, #"number", [NSNumber numberWithInt:10], #"exp_month", [NSNumber numberWithInt:2016], #"exp_year", nil];
Any advice on what do to get rid of the invalid tokens being put in this NSDictionary? Am I focusing on the wrong thing?
Thanks!
AFNetworking support only AFFormURLParameterEncoding with 1 level of parameters.
I'm writing a fix for that
replace AFQueryStringFromParametersWithEncoding implementation in AFHTTPClient by
extern NSArray * AFQueryParametersFromParametersAtBaseKeyWithEncoding(id parameters, NSString *baseKey, NSStringEncoding encoding);
extern NSArray * AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(NSDictionary *parameters, NSString *baseKey, NSStringEncoding encoding);
extern NSArray * AFQueryParametersFromParametersArrayAtBaseKeyWithEncoding(NSArray *parameters, NSString *baseKey, NSStringEncoding encoding);
extern NSArray * AFQueryStringComponentFromParameterAtBaseKeyWithEncoding(id parameter, NSString *key, NSStringEncoding encoding);
NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding) {
NSMutableArray *mutableParameterComponents = [NSMutableArray array];
[mutableParameterComponents addObjectsFromArray:AFQueryParametersFromParametersAtBaseKeyWithEncoding(parameters,nil,encoding)];
return [mutableParameterComponents componentsJoinedByString:#"&"];
}
NSArray * AFQueryParametersFromParametersAtBaseKeyWithEncoding(id parameters, NSString *baseKey, NSStringEncoding encoding)
{
NSMutableArray *mutableParameterComponents = [NSMutableArray array];
if([parameters isKindOfClass:[NSDictionary class]]) {
[mutableParameterComponents addObjectsFromArray:AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(parameters,baseKey,encoding)];
}
else if([parameters isKindOfClass:[NSArray class]]) {
[mutableParameterComponents addObjectsFromArray:AFQueryParametersFromParametersArrayAtBaseKeyWithEncoding(parameters,baseKey,encoding)];
}
else {
[mutableParameterComponents addObject:AFQueryStringComponentFromParameterAtBaseKeyWithEncoding(parameters,baseKey,encoding)];
}
return mutableParameterComponents;
}
NSArray * AFQueryParametersFromParametersDictionaryAtBaseKeyWithEncoding(NSDictionary *parameters, NSString *baseKey, NSStringEncoding encoding)
{
NSMutableArray *mutableParameterComponents = [NSMutableArray array];
id key = nil;
NSEnumerator *enumerator = [parameters keyEnumerator];
while ((key = [enumerator nextObject])) {
NSString *newKey = baseKey?[NSString stringWithFormat:#"%#[%#]",baseKey,key]:key;
[mutableParameterComponents addObjectsFromArray:AFQueryParametersFromParametersAtBaseKeyWithEncoding([parameters valueForKey:key],newKey,encoding)];
}
return mutableParameterComponents;
}
NSArray * AFQueryParametersFromParametersArrayAtBaseKeyWithEncoding(NSArray *parameters, NSString *baseKey, NSStringEncoding encoding)
{
NSMutableArray *mutableParameterComponents = [NSMutableArray array];
for (id value in parameters) {
NSString* newKey = [NSString stringWithFormat:#"%#[]",newKey];
[mutableParameterComponents addObjectsFromArray:AFQueryParametersFromParametersAtBaseKeyWithEncoding(value,newKey,encoding)];
}
return mutableParameterComponents;
}
NSArray * AFQueryStringComponentFromParameterAtBaseKeyWithEncoding(id parameter, NSString *key, NSStringEncoding encoding)
{
return [NSString stringWithFormat:#"%#=%#", AFURLEncodedStringFromStringWithEncoding([key description], encoding), AFURLEncodedStringFromStringWithEncoding([parameter description], encoding)];
}
I'm just writing the same code for multipart requests and submit a pull request to AFNetworking