Currently I am in the process of working with an API that is still in development. Due to this, the keys in the response are still changing. I have successfully been able to retrieve and parse the JSON data from the API into an NSDictionary, and then use this NSDictionary to map the values into custom objects. The approach I am using is the following
-(id)initWithDictionary:(NSDictionary*)dictionary
{
if(self = [super init]){
_ID = [dictionary valueForKey:kKEY_ID];
_name = [dictionary valueForKey:kKEY_NAME];
_nestedObject = [[NestedObject alloc]initWithDictionary:[dictionary valueForKey:kKEY_NESTED_OBJECT]];
//etc...
}
return self
}
Each nested object also contains the same parsing structure.
This works fine except for when the API changes. When something does change, required values do not exist and this causes unexpected behavior or crashes.
Ideally, if one of the keys change, I would like to produce a NSError that I can use to print the value that has changed helping me more quickly find the change and rectify it.
The only alternative approach that I have currently been able to come up with I feel is messy and unmaintainable.
-(id)initWithDictionary:(NSDictionary*)dictionary andError:(NSError**)error
{
if(self = [super init]){
BOOL _parsedSuccessfully = TRUE;
if (_parsedSuccessfully) {
_ID = [dictionary valueForKey: kKEY_ID];
if (!_ID){
_parsedSuccessfully = FALSE;
*error = [NSError parsingErrorFromKey: kKEY_ID];
}
}
if (_parsedSuccessfully) {
_name = [dictionary valueForKey: kKEY_NAME];
if (!_name){
_parsedSuccessfully = FALSE;
*error = [NSError parsingErrorFromKey: kKEY_NAME];
}
}
if (_parsedSuccessfully) {
_nestedObject = [[NestedObject alloc]initWithDictionary:[dictionary valueForKey:kKEY_NESTED_OBJECT]];
if (!_nestedObject){
_parsedSuccessfully = FALSE;
*error = [NSError parsingErrorFromKey: kKEY_NESTED_OBJECT];
}
}
//etc...
if (!_parsedSuccessfully) {
return nil;
}
}
return self
}
I was wondering if anyone else had any other better approaches that preferably uses much less duplication.
Any help would be greatly appreciated.
Add an isValid method to your object, which can be used in any situation, not just when initialised from the JSON dictionary.
- (BOOL)isValid:(NSError **)error {
#define CHECK_NOT_NULL(x, key) if (!x) { \
if (error != NULL) \
*error = [NSError parsingErrorFromKey:key]; \
return NO; \
}
#define CHECK_NOT_EMPTY(x, key) if (!x || ![x length]) { \
if (error != NULL) \
*error = [NSError parsingErrorFromKey:key]; \
return NO; \
}
CHECK_NOT_NULL(_ID, kKEY_ID);
CHECK_NOT_EMPTY(_name, kKEY_NAME);
// etc.
return YES;
#undef CHECK_NOT_NULL
#undef CHECK_NOT_EMPTY
}
And then use this in your init method:
- (id)initWithDictionary:(NSDictionary*)dictionary andError:(NSError**)error
{
if (self = [super init]) {
_ID = [dictionary valueForKey: kKEY_ID];
_name = [dictionary valueForKey: kKEY_NAME];
// etc.
if (![self isValid:error]) {
self = nil; // Assuming ARC
}
}
return self;
}
If you create an array of your keys then you can run your check in a loop so you only have one copy of the loop.
Again, using the array you could get all of the keys from the dictionary and remove them from each other. One way will give you new keys and the other way will give you the missing keys.
Related
I have a code like that
if ([dataArray valueForKey:#"success"]) {
[self.feedsArray addObjectsFromArray:dataArray];
NSLog(#"self.feedsArray: %#",self.feedsArray);
} else {
NSLog(#"no feed found ");
}
dataArray is a NSMutableArray which ultimately contains a JSON Dictionary.
but I am getting the same console output independent of success either TRUE or FALSE, but my console output is always same.my console output is:
for FALSE or NO:
self.feedsArray: (
{
action = register;
message = "Invalid parameters";
success = 0;
}
)
and for TRUE or YES:
self.feedsArray: (
{
action = register;
message = "valid parameters";
success = 1;
}
)
in both cases if part is executed.
in NSUserDefaults there is a method boolForKey but how to do this in case of NSMutableArray.
You need to read the fine print for [NSArray valueForKey:], specifically:
Returns an array containing the results of invoking valueForKey: using
key on each of the array's objects.
and:
The returned array contains NSNull elements for each object that
returns nil.
So if the array contains, say, 3 objects and none of them have a success key then you will get an array of 3 NSNull objects returned.
Therefore the if statement will fire whenever dataArray is non-empty, which is obviously not what you intended.
You should check the contents of the returned array:
BOOL succeeded = NO;
NSArray *results = [dataArray valueForKey:#"success"];
for (NSObject *obj in results) {
succeeded = [obj isKindOfClass:[NSNumber class]] && [(NSNumber *)obj boolValue];
if (succeeded)
break;
}
if (succeeded) {
[self.feedsArray addObjectsFromArray:dataArray];
NSLog(#"self.feedsArray: %#",self.feedsArray);
} else {
NSLog(#"no feed found ");
}
You can do this in simple way:
What i see in your response json value is, you have dictionary in dataArray at index 0
NSMutableDictionary *responseDict = [dataArray objectAtIndex:0];
if([[responseDict objectForKey:#"success"] boolValue])
{
NSLog(#"Success: 1");
}
{
NSLog(#"Success: 0");
}
Use index instead of key for an array.
NSDictionary dictionary = (NSDictionary *)dataArray[0];
if ([(NSNumber *)[dictionary objectForKey:#"success"] boolValue]) {
// ...
}
otherwise use if([[[dataArray objectAtIndex:0] valueForKey:#"success"] isEqualToString:#"1"])
An array does not store keys, the only way to access items in an array is by index.
You should be using an NSDictionary/NSMutableDictionary instead. If you want to use a bool store it as a NSNumber, [NSNumber numberWithBool:YES] and then use the instance method valueForBool to read it back.
Try this
if ([[dataArray valueForKey:#"success"]isEqualToString:#"1"]) {
[self.feedsArray addObjectsFromArray:dataArray];
NSLog(#"self.feedsArray: %#",self.feedsArray);
}
else {
NSLog(#"no feed found ");
}
It 'll work out.
use this if you want bool value
if([[dataArray valueForKey:#"success"] boolValue])
{
//i.e success is true
}
if response contains array of dictionaries then we can use loop and check condition,
here i is index variable of array,
if([[[dataArray objectAtIndex:i] objectForKey:#"success"] boolValue])
{
// success is true ,
}
Replace you code line
if ([dataArray valueForKey:#"success"]) {
}
with
if ([[dataArray valueForKey:#"success"] integerValue]) {
}
Hope it will work for you.
its working with replacing the line with
if ([[[dataArray objectAtIndex:0] valueForKey:#"success"] boolValue])
I'm having a strange problem while using NSDictionnary ...
I'm trying to retrieve an object for a key that is present is the dictionnary with the method objectForKey, but it returns nil instead.
When I print out the whole dictionnary, I can see clearly the key and the value I'm looking for.
Here is the code :
- (MObject *)GetWithMProperty:(MProperty *)prop {
NSLog(#"We search an object for a property named %#", prop.Name);
NSArray *keyArray = [_dict allKeys];
int count = [keyArray count];
for (int i=0; i < count; i++) {
MObject *tmp = [_dict objectForKey:[ keyArray objectAtIndex:i]];
NSLog(#"Key = %# | Object = %d", ((MProperty*)[keyArray objectAtIndex:i]).Name, tmp.GetTypeId);
if (prop == [keyArray objectAtIndex:i])
NSLog(#"Wouhou !");
else
NSLog(#"Too bad :(");
}
return [_dict objectForKey:prop];
}
And the stack trace :
2012-10-29 11:24:07.730 IOS6[1451:11303] We search an object for a property named Value
2012-10-29 11:24:07.730 IOS6[1451:11303] Key = Name | Object = 4
2012-10-29 11:24:07.731 IOS6[1451:11303] Too bad :(
2012-10-29 11:24:07.731 IOS6[1451:11303] Key = Value | Object = 0
2012-10-29 11:24:07.732 IOS6[1451:11303] Too bad :(
It's a bit complicated, I'm using J2ObjC to compile a fully functional Engine, and thus I can't modify the classes MProperty and MObject (used by the Engine).
MProperty doesn't conforms to NSCopying protocol, so I created a classe called IPhoneMProperty that inherits from MProperty and conforms to the protocol.
Here is this class :
#implementation IPhoneMProperty
- (id)initWithMProperty:(MProperty *)prop {
self = [super initWithInt:prop.OwnerTypeId withNSString:prop.Name withInt:prop.TypeId withMBasicValue:prop.DefaultValue withInt:prop.Flags];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
IPhoneMProperty *prop = [[IPhoneMProperty alloc] initWithMProperty:self];
return prop;
}
#end
And the method I use to add object and keys to the dictionnary :
- (void)SetWithMProperty:(MProperty *)prop withMObject:(MObject *)obj {
IPhoneMProperty *tempKey = [[IPhoneMProperty alloc] initWithMProperty:prop];
[_dict setObject:obj forKey:tempKey];
}
I hope it's clear enough, actually it's the only solution I found for the moment, but it doesn't work :(
Can anyone helps me with this ?
Thanks !
The problem exists in the line
if (prop == [keyArray objectAtIndex:i])
Instead, implement isEquals: method in your MProperty class.
-(BOOL)isEquals:(MProperty*)inProp {
if( [inProp.name isEqualToString:self.name] )return YES;
return NO;
}
And, here, instead of the line
if (prop == [keyArray objectAtIndex:i])
use the following line,
if ([prop isEquals [keyArray objectAtIndex:i]])
Have you allocated your _dict object before using?
modify your code as follows & check.
- (void)SetWithMProperty:(MProperty *)prop withMObject:(MObject *)obj {
IPhoneMProperty *tempKey = [[IPhoneMProperty alloc] initWithMProperty:prop];
if(!_dict)
_dict = [[NSMutableDictionay alloc]init];
[_dict setObject:obj forKey:tempKey];
}
Hope it works for you.
Can you try changing the if condition to compare betweenstring values ? Like this :
if ([prop.Name isEqualToString:((MProperty*)[keyArray objectAtIndex:i]).Name])
NSLog(#"Wouhou !");
else
NSLog(#"Too bad :(");
}
I am getting server response and parsing as below for my synchronous request.
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonError];
NSLog(#"responseDict: %#", responseDict);
The result is below:
responseDict: {
d = {
Datavalue = 1;
"__type" = "Response:#DummyAPI";
response = OK;
};
I am trying to parse the above result, if it is "OK" then I want to store the "Datavalue" somewhere..
I am trying like below,
-(void) handleResponse :(NSDictionary *) responsedata // it is being passed from caller
{
NSString* value = NULL;
for (id key in responsedata)
{
value = (NSString*)[responsedata objectForKey:#"response"];
if ( [value isEqualToString:#"OK"] )
{
NSLog(#"RESPONSE SUCCESS, need to store data value");
}
else
{
NSLog(#"INVALID RESPONSE");
}
}
}
But, it is always printing "INVALID RESPONSE", but I got response as "OK". What am I doing wrong here? Please advise!
Thank you!
You are missing a level of nesting in the dictionaries you received back. Your response shows that responseData contains a key called "d" whose value is another dictionary, and that dictionary is what has a key called "response"
Because you are working inside a loop, I will assume that your response can contain multiple values at the top level and not just "d", and that you are trying to loop through each of those. Based on that assumption you probably want something like this:
-(void) handleResponse :(NSDictionary *) responsedata // it is being passed from caller
{
NSString* value = NULL;
for (id key in responsedata)
{
NSDictionary *currentDict = (NSDictionary *) [responseData objectForKey:key];
value = (NSString*)[currentDict objectForKey:#"response"];
if ( [value isEqualToString:#"OK"] )
{
NSLog(#"RESPONSE SUCCESS, need to store data value");
}
else
{
NSLog(#"INVALID RESPONSE");
}
}
}
Seems weird to me and probably is not the problem but:
value = (NSString*)[responsedata objectForKey:#"response"];
if ( [value isEqualToString:#"OK"] ){
NSLog(#"RESPONSE SUCCESS, need to store data value");
}
else{
NSLog(#"INVALID RESPONSE");
}
That shouldn't been done inside a for loop. Just try that code outside the for loop, it should work. Other thing you might want to try doing is calling the isKindOfClass method over the value for #"response" and you should get something saying it is a string.
Ok so i have been trying to write a simple keychain module for titanium in xcode for sometime now and still i am unable to get it right. when i run the program in xcode it says build succeeded but does not open the emulator to run it. i started commenting out code to see which methods were causing problems and the emulator runs fine when i comment out these two methods. i am new to objective c and writing modules so any advice would be great. My main question is can you see anything wrong with these two methods. Any input or advice is greatly appreciated.
+ (BOOL)setString:(NSString *)string forKey:(NSString *)key {
if (string == nil || key == nil) {
return NO;
}
key = [NSString stringWithFormat:#"%# - %#", [Keychain appName], key];
// First check if it already exists, by creating a search dictionary and requesting that
// nothing be returned, and performing the search anyway.
NSMutableDictionary *existsQueryDictionary = [NSMutableDictionary dictionary];
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
[existsQueryDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
// Add the keys to the search dict
[existsQueryDictionary setObject:#"service" forKey:(id)kSecAttrService];
[existsQueryDictionary setObject:key forKey:(id)kSecAttrAccount];
OSStatus res = SecItemCopyMatching((CFDictionaryRef)existsQueryDictionary, NULL);
if (res == errSecItemNotFound) {
if (string != nil) {
NSMutableDictionary *addDict = existsQueryDictionary;
[addDict setObject:data forKey:(id)kSecValueData];
res = SecItemAdd((CFDictionaryRef)addDict, NULL);
NSAssert1(res == errSecSuccess, #"Recieved %d from SecItemAdd!", res);
}
} else if (res == errSecSuccess) {
// Modify an existing one
// Actually pull it now of the keychain at this point.
NSDictionary *attributeDict = [NSDictionary dictionaryWithObject:data forKey:(id)kSecValueData];
res = SecItemUpdate((CFDictionaryRef)existsQueryDictionary, (CFDictionaryRef)attributeDict);
NSAssert1(res == errSecSuccess, #"SecItemUpdated returned %d!", res);
} else {
NSAssert1(NO, #"Received %d from SecItemCopyMatching!", res);
}
return YES;
}
+ (NSString *)getStringForKey:(NSString *)key {
key = [NSString stringWithFormat:#"%# - %#", [Keychain appName], key];
NSMutableDictionary *existsQueryDictionary = [NSMutableDictionary dictionary];
[existsQueryDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
// Add the keys to the search dict
[existsQueryDictionary setObject:#"service" forKey:(id)kSecAttrService];
[existsQueryDictionary setObject:key forKey:(id)kSecAttrAccount];
// We want the data back!
NSData *data = nil;
[existsQueryDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
OSStatus res = SecItemCopyMatching((CFDictionaryRef)existsQueryDictionary, (CFTypeRef *)&data);
[data autorelease];
if (res == errSecSuccess) {
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
return string;
} else {
NSAssert1(res == errSecItemNotFound, #"SecItemCopyMatching returned %d!", res);
}
return nil;
}
Where are you calling these methods from? Are they in your main module? If you can show me what you want your final JavaScript calls to look like, I can address your problem with more confidence.
One immediate problem that I see is that you can't send primitive types (BOOL, for instance) back to Titanium. You need to convert it to a number first. (Fear not, JavaScript and its truthy values can still use it like a BOOL!) There's a macro to help you past that -- return a NSNumber*, and wrap your actual returns like this: return NUMBOOL(YES); or return NUMBOOL(NO);.
Another could be your arguments. Kroll is going to call your methods with a single argument, from which you can retrieve the arguments that were given to you. Your method signatures will normally look like this, if exposed to JavaScript: -(void)mySpecialMethod:(id)args;
A third issue could be the name of your methods. "get" and "set" are special keywords to Kroll, and are used on properties. From your JavaScript you would write myModule.property = 'something', which then calls -(void)setProperty:(id)args in your objective-c.
Finally, I'm not sure why you have these declared as class level methods, vs object level methods. Perhaps if you can explain more about where these methods are being used, I can see what you are trying to do and help you get there.
Past that, you should take a look at the core source code for Titanium Mobile to learn more about what you can and cannot do with your objective-c in your own modules.
Hope this helps! -Dawson
I am having a NSMutableDictionary. I have to dynamically rename any Key in the dictionary to a new value, in my code.. I can't find any built-in API to do this..
How can I do this? Is there any built-in API available to do this?
Thanks everyone..
// assumes that olkdey and newkey won't be the same; they can't as
// constants... but...
[dict setObject: [dict objectForKey: #"oldkey"] forKey: #"newkey"];
[dict removeObjectForKey: #"oldkey"];
Think about what "directly editing an existing key" means. A dictionary is a hash; it hashes the contents of the keys to find a value.
What happens if you were to change the contents of a key? The key would need to be rehashed (and the dictionary's internal structures re-balanced) or the value would no longer be retrievable.
Why do you want to edit the contents of a key in the first place? I.e. what problem does that solve that the above does not?
This should work:
- (void) renameKey:(id<NSCopying>)oldKey toKey:(id<NSCopying>)newKey{
NSObject *object = [dictionary objectForKey:oldKey];
[object retain];
[dictionary removeObjectForKey:oldKey];
[dictionary setObject:object forKey:newKey];
[object release];
}
This does exactly the same as bbum's answer but, if you remove the old key first (like in this example) then you have to retain the object temporarily otherwise it might get deallocated in the way ;)
Conclusion: Unless you need explicitly to remove the old key first do as bbum.
#interface NSMutableDictionary (KAKeyRenaming)
- (void)ka_replaceKey:(id)oldKey withKey:(id)newKey;
#end
#implementation NSMutableDictionary (KAKeyRenaming)
- (void)ka_replaceKey:(id)oldKey withKey:(id)newKey
{
id value = [self objectForKey:oldKey];
if (value) {
[self setObject:value forKey:newKey];
[self removeObjectForKey:oldKey];
}
}
#end
This also handles the case where the dictionary doesn't have a value for the key nicely.
I have to navigate a complete JSON response object that holds fields, sub-dictionaries and sub-arrays. All because one of the JSON fields is called "return" which is an iOS reserved word, so can't be used with the JSONModel Cocoa Pod.
Here's the code:
+ (id) sanitizeJSON:(id) dictIn {
if (dictIn) //check not null
{
// if it's a dictionary item
if ([dictIn isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *dictOut = [dictIn mutableCopy];
// Do the fix replace "return" with "not_return"
if ([dictOut objectForKey: #"return"])
{[dictOut setObject: [dictIn objectForKey: #"return"] forKey: #"not_return"];
[dictOut removeObjectForKey: #"return"];}
// Continue the recursive walk through
NSArray*keys=[dictOut allKeys]; //get all the keys
for (int n=0;n<keys.count;n++)
{
NSString *key = [keys objectAtIndex:n];
//NSLog(#"key=%# value=%#", key, [dictOut objectForKey:key]);
if (([[dictOut objectForKey:key] isKindOfClass:[NSDictionary class]]) || ([[dictOut objectForKey:key] isKindOfClass:[NSArray class]]))
{
// recursive call
id sanitizedObject = [self sanitizeJSON:[dictOut objectForKey:key]];
[dictOut removeObjectForKey: key];
[dictOut setObject:sanitizedObject forKey:key];
// replace returned (poss modified) item with this one
}
}
return dictOut; //return dict
}
else if ([dictIn isKindOfClass:[NSArray class]]) //Or if it's an array item
{
NSMutableArray *tempArray = [dictIn mutableCopy];
// Do the recursive walk across the array
for (int n=0;n< tempArray.count; n++)
{
// if array item is dictionary
if (([[tempArray objectAtIndex:n] isKindOfClass:[NSDictionary class]]) || ([[tempArray objectAtIndex:n] isKindOfClass:[NSArray class]]))
{
// recursive call
id sanitizedObject = [self sanitizeJSON:[tempArray objectAtIndex:n]];
// replace with the possibly modified item
[tempArray replaceObjectAtIndex:n withObject:sanitizedObject];
}
}
return tempArray; //return array
}
return dictIn; //Not nil or dict or array
}
else
return dictIn; //return nil
}