Refining description of NSDictionary - objective-c

I want to see objects classes of my dictionary in console log. As for standard NSObject subclasses, I override -(NSString*) description in category:
-(NSString*) description
{
NSMutableString* desc = [NSMutableString stringWithFormat: #"<%# 0x%08x>\nobjects count: %ld", [self class], (uint)self, [self count]];
for (id key in [self allKeys])
[desc appendFormat: #"\n%# = %# (%#)", key, [self objectForKey: key], [[self objectForKey: key] class]];
return desc;
}
It works, but only for top-level NSDictionary object (if the object has dictionaries in children they are logged bypassing description method). So NSDictionary prints its children objects in some way without calling description on them...
Is there an approach to log these children dictionaries through my description method?
PS: In practical situation I want to find an object in dictionary that can't be saved to plist. Maybe there is another solution, I would be thankful for that too.

You can write a recursive description method:
// Private Methods
#interface MyClass ()
- (NSString *)_description:(id)object;
#end
...
- (NSString *)_description:(id)object
{
if ([object isKindOfClass:[NSDictionary class]])
{
NSDictionary *dict = (NSDictionary *)object;
NSMutableString *desc = [NSMutableString stringWithFormat: #"<%# %p>\nobjects count: %ld", [dict class], dict, [dict count]];
for (id key in [dict allKeys])
{
[desc appendFormat: #"\n%# = %# (%#)", key, [self _description:[objectForKey: key]], [[self objectForKey: key] class]];
return desc;
}
}
else
{
return [(NSObject *)object description];
}
}
- (NSString *)description
{
return [self _description:self];
}
You'll probably want to pass an incrementing indentation counter so you can format the child objects better, but you should get the idea.

Related

Looping through nested NSDictionary

I know there is a somewhat simple solution here, I can't quite wrap my head around it though.
I have a user generated .plist file that can have a (reasonable) infinite amount of nested trees in it. What I'm wanting to do is create a folder structure based on how the plist is nested.
Since I do not know how deep the main dictionary goes I cant figure out how to loop through it and tell it to create a directory then dive into that and create directories within it.
I know how to create folders its iterating through the entire list is what is throwing me off. I'm pretty sure I need a separate recursive method just am not sure where to begin. Key "Children" and "Name" are what I'm using to create the list. Any help would be great.
I'd start with defining a path to the directory where you want to create the directory structure. Then call the function to build the directory structure.
NSString *pathToDirectory = #"./";
[self buildDirectoriesAtPath: pathToDirectory fromDictionary: dictionary];
The function -buildDirectoriesAtPath:fromDictionary: is recursive and would look something like this:
- (void) buildDirectoriesAtPath: (NSString *) filepath fromDictionary: (NSDictionary *) dictionary {
for (NSString *key in dictionary.allKeys) {
if ([key isEqualToString: #"Children"]) {
NSArray *children = [dictionary objectForKey: key];
for (id object in children) {
if ([object isKindOfClass: [NSDictionary class]]) {
NSDictionary *directoryInfo = (NSDictionary *) object;
NSString *directoryName = [directoryInfo objectForKey: #"Name"];
NSString *directoryPath = [NSString stringWithFormat: #"%#%#", filepath, directoryName];
// Create directory
NSLog(#"Creating directory: %#", directoryPath);
// Create subdirectories
NSArray *subdirectories = [directoryInfo objectForKey: #"Children"];
for (id directory in subdirectories) {
if ([directory isKindOfClass: [NSString class]]) {
NSString *subdirectoryName = (NSString *) directory;
NSString *subdirectoryPath = [NSString stringWithFormat: #"%#/%#", directoryPath, subdirectoryName];
// Create directory
NSLog(#"Creating directory: %#", subdirectoryPath);
}
else if ([directory isKindOfClass: [NSDictionary class]]) {
NSDictionary *subdirectory = (NSDictionary *) directory;
NSString *subdirectoryName = [subdirectory objectForKey: #"Name"];
NSString *subdirectoryPath = [NSString stringWithFormat: #"%#/%#/", directoryPath, subdirectoryName];
[self buildDirectoriesAtPath: subdirectoryPath fromDictionary: (NSDictionary *) directory];
}
}
}
else if ([object isKindOfClass: [NSString class]]) {
NSString *directoryName = (NSString *) object;
NSString *directoryPath = [NSString stringWithFormat: #"%#%#", filepath, directoryName];
// Create directory
NSLog(#"Creating directory: %#", directoryPath);
}
}
}
}
}
If there's anything you'd like clarified in this, don't hesitate to ask. :)
May be this function will help you,
void recursiveCall (NSDictionary *dictionary) {
NSArray *_keys = [dictionary allKeys];
for (NSString *_key in _keys) {
id obj = [dictionary valueForKey:_key];
NSLog(#"Create folder of %#",_key);
if ([obj isKindOfClass:[NSArray class]]) {
for (id _obj in obj) {
if ([_obj isKindOfClass:[NSDictionary class]]) {
recursiveCall(_obj);
}
else {
NSLog(#"Create folder of %#",_obj);
}
}
}
}
}

Obj-C easy method to convert from NSObject with properties to NSDictionary?

I ran across something that I eventually figured out, but think that there's probably a much more efficient way to accomplish it.
I had an object (an NSObject which adopted the MKAnnotation protocol) that had a number of properties (title, subtitle,latitude,longitude, info, etc.). I needed to be able to pass this object to another object, which wanted to extract info from it using objectForKey methods, as an NSDictionary (because that's what it was getting from another view controller).
What I ended up doing was create a new NSMutableDictionary and use setObject: forKey on it to transfer each piece of vital info, and then I just passed on the newly created dictionary.
Was there an easier way to do this?
Here's the relevant code:
// sender contains a custom map annotation that has extra properties...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetailFromMap"])
{
DetailViewController *dest =[segue destinationViewController];
//make a dictionary from annotaion to pass info
NSMutableDictionary *myValues =[[NSMutableDictionary alloc] init];
//fill with the relevant info
[myValues setObject:[sender title] forKey:#"title"] ;
[myValues setObject:[sender subtitle] forKey:#"subtitle"];
[myValues setObject:[sender info] forKey:#"info"];
[myValues setObject:[sender pic] forKey:#"pic"];
[myValues setObject:[sender latitude] forKey:#"latitude"];
[myValues setObject:[sender longitude] forKey:#"longitude"];
//pass values
dest.curLoc = myValues;
}
}
Thanks in advance for your collective wisdom.
Here's what I came up with, thanks to the folks, below...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetailFromMap"])
{
DetailViewController *dest =[segue destinationViewController];
NSArray *myKeys = [NSArray arrayWithObjects:
#"title",#"subtitle",#"info",#"pic",#"latitude",#"longitude", nil];
//make a dictionary from annotaion to pass info
NSDictionary *myValues =[sender dictionaryWithValuesForKeys:myKeys];
//pass values
dest.curLoc = myValues;
}
}
And a even simpler fix, as seen below...
Using valueForKey instead of object for key to retrieve the information.
Sure thing! Use the objc-runtime and KVC!
#import <objc/runtime.h>
#interface NSDictionary(dictionaryWithObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id) obj;
#end
#implementation NSDictionary(dictionaryWithObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
#end
And you would use like this:
MyObj *obj = [MyObj new];
NSDictionary *dict = [NSDictionary dictionaryWithPropertiesOfObject:obj];
NSLog(#"%#", dict);
This is an old post and Richard J. Ross III's answer is really helpful, but in case of custom objects (an custom class has another custom object in it). However, sometimes properties are other objects and so forth, making the serialization a bit complicated.
Details * details = [[Details alloc] init];
details.tomato = #"Tomato 1";
details.potato = #"Potato 1";
details.mangoCount = [NSNumber numberWithInt:12];
Person * person = [[Person alloc]init];
person.name = #"HS";
person.age = #"126 Years";
person.gender = #"?";
person.details = details;
For converting these type of objects (multiple custom objects) into dictionary, I had to modify Richard J. Ross III's Answer a little bit.
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
Class classObject = NSClassFromString([key capitalizedString]);
if (classObject) {
id subObj = [self dictionaryWithPropertiesOfObject:[obj valueForKey:key]];
[dict setObject:subObj forKey:key];
}
else
{
id value = [obj valueForKey:key];
if(value) [dict setObject:value forKey:key];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
I hope it will help someone. Full credit goes to Richard J. Ross III.
If the properties had the same names as the keys used to access the dictionary then you could have just used KVC and had valueForKey: instead of objectForKey.
For example given this dictionary
NSDictionary *annotation = [[NSDictionary alloc] initWithObjectsAndKeys:
#"A title", #"title", nil];
and this Object
#interface MyAnnotation : NSObject
#property (nonatomic, copy) NSString *title;
#end
it wouldn't matter if I had an instance of the dictionary or MyAnnotation I could call
[annotation valueForKey:#"title"];
Obviously that works the other way as well e.g.
[annotation setValue:#"A title" forKey:#"title"];
To complete the method of Richard J. Ross, this one works with NSArray of custom object.
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
Class classObject = NSClassFromString([key capitalizedString]);
id object = [obj valueForKey:key];
if (classObject) {
id subObj = [self dictionaryWithPropertiesOfObject:object];
[dict setObject:subObj forKey:key];
}
else if([object isKindOfClass:[NSArray class]])
{
NSMutableArray *subObj = [NSMutableArray array];
for (id o in object) {
[subObj addObject:[self dictionaryWithPropertiesOfObject:o] ];
}
[dict setObject:subObj forKey:key];
}
else
{
if(object) [dict setObject:object forKey:key];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
There are so many solutions and nothing worked for me as I had a complex nested object structure. This solution takes things from Richard and Damien but improvises as Damien's solution is tied to naming keys as class names.
Here is the header
#interface NSDictionary (PropertiesOfObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj;
#end
Here is the .m file
#implementation NSDictionary (PropertiesOfObject)
static NSDateFormatter *reverseFormatter;
+ (NSDateFormatter *)getReverseDateFormatter {
if (!reverseFormatter) {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
reverseFormatter = [[NSDateFormatter alloc] init];
[reverseFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[reverseFormatter setLocale:locale];
}
return reverseFormatter;
}
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id object = [obj valueForKey:key];
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
NSMutableArray *subObj = [NSMutableArray array];
for (id o in object) {
[subObj addObject:[self dictionaryWithPropertiesOfObject:o]];
}
dict[key] = subObj;
}
else if ([object isKindOfClass:[NSString class]]) {
dict[key] = object;
} else if ([object isKindOfClass:[NSDate class]]) {
dict[key] = [[NSDictionary getReverseDateFormatter] stringFromDate:(NSDate *) object];
} else if ([object isKindOfClass:[NSNumber class]]) {
dict[key] = object;
} else if ([[object class] isSubclassOfClass:[NSObject class]]) {
dict[key] = [self dictionaryWithPropertiesOfObject:object];
}
}
}
return dict;
}
#end
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];

How to return arrays object + count IOS

hi at all ,I've this code :
+(NSArray *)splatterUrls
{
NSString *jsonString = [ ApiMethod jsonOfStores];
NSDictionary *results =[jsonString objectFromJSONString];
NSArray *movieArray = [results objectForKey:#"Seasons"];
//int i=0;
// Search for year to match
for (NSDictionary *movie in movieArray)
{
NSNumber *idSplatterMovie = [movie objectForKey:#"Id"];
// NSLog(#" %#", idSplatterMovie );
NSArray *try = [movie objectForKey:#"Episodes"];
// NSLog(#"%#", try);
for (NSDictionary *op in try)
{
if([idSplatterMovie integerValue] == 46)
{
//i++;
NSArray *movieArrayString = [op objectForKey:#"Url"];
// NSLog(#" %#", movieArrayString);
return movieArrayString;
}
}
}
}
I want to return movieArrayString with all his objects and how many object contains in it. I think that I should use this method : + (id)arrayWithObjects:(const id *)objects count:(NSUInteger)count. It's possible? If yes, can you tell me how can use it?
Thank you so much!
by the way , i have to call splatterUrls method and implement in home.m that it is :
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *urlSplatter= [GetSplatterUrlsMovie splatterUrls];
NSLog(#" %#", urlSplatter);
}
Looks good as it is to me.
Do this to return your movies array, array will be equal to your movies array:
NSArray *array = [self splatterUrls];
Then to get the count/number of objects in your array do this, i is equal to the number of objects in the array:
int i = [array count];
What is the problem ??
You return a NSarray ... call the method count on your NSarray object!

changing a key name in NSDictionary

I have a method which returns me a nsdictionary with certain keys and values. i need to change the key names from the dictionary to a new key name but the values need to be same for that key,but i am stuck here.need help
This method will only work with a mutable dictionary. It doesn't check what should be done if the new key already exists.
You can get a mutable dictionary of a immutable by calling mutableCopy on it.
- (void)exchangeKey:(NSString *)aKey withKey:(NSString *)aNewKey inMutableDictionary:(NSMutableDictionary *)aDict
{
if (![aKey isEqualToString:aNewKey]) {
id objectToPreserve = [aDict objectForKey:aKey];
[aDict setObject:objectToPreserve forKey:aNewKey];
[aDict removeObjectForKey:aKey];
}
}
You can't change anything in an NSDictionary, since it is read only.
How about loop through the dictionary and create a new NSMutableDictionary with the new key names ?
Could you not add a new key-value pair using the old value, and then remove the old key-value pair?
This would only work on an NSMutableDictionary. NSDictionarys are not designed to be changed once they have been created.
To change specific key to new key, I have written a recursive method for Category Class.
- (NSMutableDictionary*)replaceKeyName:(NSString *)old_key with:(NSString )new_key {
NSMutableDictionary dict = [NSMutableDictionary dictionaryWithDictionary: self];
NSMutableArray *keys = [[dict allKeys] mutableCopy];
for (NSString key in keys) {
if ([key isEqualToString:old_key]) {
id val = [self objectForKey:key];
[dict removeObjectForKey:key];
[dict setValue:val forKey:new_key];
return dict;
} else {
const id object = [dict objectForKey: key];
if ([object isKindOfClass:[NSDictionary class]]) {
[dict setObject:[dict replaceKeyName:old_key with:new_key] forKey:key];
} else if ([object isKindOfClass:[NSArray class]]){
if (object && [(NSArray)object count] > 0) {
NSMutableArray *arr_temp = [[NSMutableArray alloc] init];
for (NSDictionary *temp_dict in object) {
NSDictionary *temp = [temp_dict replaceKeyName:old_key with:new_key];
[arr_temp addObject:temp];
}
[dict setValue:arr_temp forKey:key];
}
}
}
}
return dict;
}

Deep combine NSDictionaries

I need to merge two NSDictionarys into one provided that if there are dictionaries within the dictionaries, they are also merged.
More or less like jQuery's extend function.
NSDictionary+Merge.h
#import <Foundation/Foundation.h>
#interface NSDictionary (Merge)
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2;
- (NSDictionary *) dictionaryByMergingWith: (NSDictionary *) dict;
#end
NSDictionary+Merge.m
#import "NSDictionary+Merge.h"
#implementation NSDictionary (Merge)
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2 {
NSMutableDictionary * result = [NSMutableDictionary dictionaryWithDictionary:dict1];
[dict2 enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
if (![dict1 objectForKey:key]) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict1 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
} else {
[result setObject: obj forKey: key];
}
}
}];
return (NSDictionary *) [[result mutableCopy] autorelease];
}
- (NSDictionary *) dictionaryByMergingWith: (NSDictionary *) dict {
return [[self class] dictionaryByMerging: self with: dict];
}
#end
I think this is what you're looking for:
First, you need to make a deep mutable copy, so you can create a category on NSDictionary to do this:
#implementation NSDictionary (DeepCopy)
- (id)deepMutableCopy
{
id copy(id obj) {
id temp = [obj mutableCopy];
if ([temp isKindOfClass:[NSArray class]]) {
for (int i = 0 ; i < [temp count]; i++) {
id copied = [copy([temp objectAtIndex:i]) autorelease];
[temp replaceObjectAtIndex:i withObject:copied];
}
} else if ([temp isKindOfClass:[NSDictionary class]]) {
NSEnumerator *enumerator = [temp keyEnumerator];
NSString *nextKey;
while (nextKey = [enumerator nextObject])
[temp setObject:[copy([temp objectForKey:nextKey]) autorelease]
forKey:nextKey];
}
return temp;
}
return (copy(self));
}
#end
Then, you can call deepMutableCopy like this:
NSMutableDictionary *someDictionary = [someDict deepMutableCopy];
[someDictionary addEntriesFromDictionary:otherDictionary];
I added this to the code mentioned above. It may not be fully correct, but it handles the case where 2 dict has an element that 1 dict does not.
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2 {
NSMutableDictionary * result = [NSMutableDictionary dictionaryWithDictionary:dict1];
NSMutableDictionary * resultTemp = [NSMutableDictionary dictionaryWithDictionary:dict1];
[resultTemp addEntriesFromDictionary:dict2];
[resultTemp enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
if ([dict1 objectForKey:key]) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict1 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
} else {
[result setObject: obj forKey: key];
}
}
else if([dict2 objectForKey:key])
{
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict2 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
} else {
[result setObject: obj forKey: key];
}
}
}];
return (NSDictionary *) [[result mutableCopy] autorelease];
}
I came here looking for a copy of jQuery's extend but I ended up writing my own implementation. It's a super simple implementation, I did it so I'd understand a way to do it.
+(NSDictionary*) dictionaryByExtending:(NSDictionary*)baseDictionary WithDictionary:(NSDictionary*)extensionDictionary {
NSMutableDictionary * resultDictionary = [NSMutableDictionary dictionaryWithDictionary:baseDictionary];
[extensionDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
BOOL isDict = [obj isKindOfClass:[NSDictionary class]];
BOOL hasValue = [baseDictionary hasObjectForKey:key] != nil;
id setObj = obj;
if( hasValue && isDict ) {
BOOL hasDict = [[baseDictionary objectForKey:key] isKindOfClass:[NSDictionary class]];
if( hasDict ) {
NSDictionary * extendedChildDictionary = [NSDictionary dictionaryByExtending:[baseDictionary objectForKey:key] WithDictionary:obj];
setObj = extendedChildDictionary;
}
}
[resultDictionary setObject:setObj forKey:key];
}];
return resultDictionary;
}
-(NSDictionary*) dictionaryByExtendingWithDictionary:(NSDictionary*)extensionDictionary {
return [NSDictionary dictionaryByExtending:self WithDictionary:extensionDictionary];
}
Hopefully someone will find this helpful, it worked in my tests with deep-recursion. I'm using it to extend deep JSON files full of text.
Alexsander Akers works for me except the case where dict2 contains a dictionary that's missing from dict1 - it crashes. I changed the logic to this:
+ (NSDictionary *) dictionaryByMerging: (NSDictionary *) dict1 with: (NSDictionary *) dict2 {
NSMutableDictionary * result = [NSMutableDictionary dictionaryWithDictionary:dict1];
[dict2 enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
if (![dict1 objectForKey:key]) {
[result setObject: obj forKey: key];
} else if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary * newVal = [[dict1 objectForKey: key] dictionaryByMergingWith: (NSDictionary *) obj];
[result setObject: newVal forKey: key];
}
}];
return (NSDictionary *) [result mutableCopy];
}
I know this is an old question, but I need to do the same thing: recursively merge two dictionary objects. I need to go a step further and merge any objects that can be merged recursively (the end goal is merging two dictionaries created from plists). I am hosting my solution at https://github.com/bumboarder6/NSDictionary-merge
I am still working on the project, but as of this writing it already works (in limited testing) for recursive dictionary merging. Arrays and Sets are coming soon.
I noticed a few logic errors in some other solutions I have seen for this problem and I hopefully avoided those pitfalls, but critiques are welcome.
Usage is simple:
#import "NSMutableDictionary-merge.h"
NSMutableDictionary* dict1 = [NSMutableDictionary ...];
NSDictionary* dict2 = [NSDictionary ...];
[dict1 mergeWithDictionary:dict2];
#import "NSDictionary+Merge.h"
#implementation NSDictionary (Merge)
+ (NSDictionary *)dictionaryByMerging:(NSDictionary *)src with:(NSDictionary *)new
{
NSMutableDictionary *result = [src mutableCopy];
[new enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSDictionary class]]
&& [src[key] isKindOfClass:[NSDictionary class]]) {
result[key] = [src[key] dictionaryByMergingWith:obj];
} else {
result[key] = obj;
}
}];
return [NSDictionary dictionaryWithDictionary:result];
}
- (NSDictionary *)dictionaryByMergingWith:(NSDictionary *)dict {
return [[self class] dictionaryByMerging:self with:dict];
}
#end
I needed a way to recursively merge (append) objects within two JSON objects, focusing on the NSDictionaries within, but also considering NSArrays, and gracefully handling when types don't match along the way. The other answers here didn't go that far, and so I needed to write it myself. The following handles all those cases. Because the validation is at the top rather than in the middle it is usable starting with mixed nonnull and nullable objects. It could be expanded in the future to support additional types where appending may apply. To use, rename the xxx_ prefix to your own three digit prefix in lowercase. This is appropriate since this is an extension to a foundation class:
NSObject+Append.h
#interface NSObject (Append)
+ (nullable id)xxx_objectAppendingObject1:(nullable id)object1 object2:(nullable id)object2 NS_SWIFT_NAME(kva_objectAppending(object1:object2:));
#end
NSObject+Append.m
#implementation NSObject (Append)
+ (nullable id)xxx_objectAppendingObject1:(nullable id)object1 object2:(nullable id)object2
{
// VALIDATE ELSE RETURN
if (object1 == nil)
{
return object2;
}
if (object2 == nil)
{
return object1;
}
// MAIN
// dictionary1
NSDictionary *dictionary1 = [object1 isKindOfClass:NSDictionary.class] ? (NSDictionary *)object1 : nil;
// dictionary2
NSDictionary *dictionary2 = [object2 isKindOfClass:NSDictionary.class] ? (NSDictionary *)object2 : nil;
// array1
NSArray *array1 = [object1 isKindOfClass:NSArray.class] ? (NSArray *)object1 : nil;
// array2
NSArray *array2 = [object2 isKindOfClass:NSArray.class] ? (NSArray *)object2 : nil;
// A. NSDICTIONARY TO NSDICTIONARY
if ((dictionary1 != nil) && (dictionary2 != nil))
{
NSMutableDictionary *returnDictionary = dictionary1.mutableCopy;
[dictionary2 enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
{
returnDictionary[key] = [self.class kva_objectAppendingObject1:dictionary1[key] object2:obj];
}];
return returnDictionary;
}
// B. NSARRAY TO NSARRAY
if ((array1 != nil) && (array2 != nil))
{
return [array1.mutableCopy arrayByAddingObjectsFromArray:array2];
}
// DEFAULT
return object2;
}
#end