NSJSONSerialization not creating mutable containers - objective-c

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;
}

Related

How to check the NSString contains URL or string data?

I am fresher to iOS, i am getting problem at checking string object contains URL or string?
NSMutableArray *Arr=[NSMutableArray alloc]initWithObject:#"Welcome", #"http://abcd.com/Images/bus.png", nil];
int i;
i++;
NSString *str=[Arr objectAtIndex:i];
Now, i want to check condition, if string contains "Welcome", have to display on label or if it is URL , i need to display that URL image in ImageView. So how can i check it? Please help me in this problem.
Instead of initiating both as NSStrings, try differentiating between them by making urls a NSURL (special container specifically for urls):
NSMutableArray* Arr = [NSMutableArray alloc]initWithObject:#"Welcome", [NSURL URLWithString:#"http://abcd.com/Images/bus.png"], nil];
for(id object in Arr)
{
if([object isKindOfClass:[NSString class]])
{
NSString* string = object;
NSLog(#"String: %#", string);
}
else if([object isKindOfClass:[NSURL class]])
{
NSURL* url = object;
NSLog(#"URL: %#", url);
}
}
Try like this
NSMutableArray *Arr=[[NSMutableArray alloc]initWithObjects:#"Welcome", #"http://abcd.com/Images/bus.png",nil];
NSString *st=nil;
for(NSString *string in Arr)
{
NSArray *matches = [detector
matchesInString:string
options:0
range:NSMakeRange(0,
[string length])];
for (NSTextCheckingResult *match in
matches) {
if ([match resultType] ==
NSTextCheckingTypeLink) {
NSURL *url = [match URL];
} else
{
NSlog(#"it is a string");
}
}
}
Try this, it will help you:
NSMutableArray *Arr=[[NSMutableArray alloc]initWithObjects:#"Welcome", #"http://abcd.com/Images/bus.png", nil];
if([Arr count])
{
for (NSString *str in Arr)
{
if([str isEqualToString:#"Welcome"])
{
NSLog(#"str is %#",str);
//do whatever you want
}
if([str isEqualToString:#"http://abcd.com/Images/bus.png"])
{
NSLog(#"str is %#",str);
//do whatever you want
}
}
}
To check NSString is containing a URL You can Try This code
if ([stringName hasPrefix:#"http://"] || [stringName hasPrefix:#"https://"]) {
//show imageVivew
}

iPhone get data from plist

English is not my native language; please excuse typing errors.
I have read similar questions but I couldn't find anything that could solve my problem..
I am making an iPhone app that shows a list of notes in a table view, and user can add and delete note.
When I add a new note to a server, I get a plist which looks like this:
<plist>
<dict>
<key>success</key><true />
<key>note</key><string>new note</string>
</dict>
</plist>
After user taps the add button, I request a connection to the server, and I do,
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[resultData appendData:data];
}
and then
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSError *sendError;
NSArray *result = [NSPropertyListSerialization propertyListWithData:resultData options: NSPropertyListMutableContainersAndLeaves format:NULL error:&sendError];
if([result count] > 0){
for(NSDictionary *dict in result){
for(NSDictionary *dict in result){
// This gives me an error "objectForKey: unrecognized selector to instance"
NSLog(#"%#", [dict objectForKey:#"success"]);
}
}
}
}
I thought that dict may not be a dictionary, so I tried NSLog(#"%#", dict); and it shows just success. But, I still don't know how I can get the value of success.
Try this ::
.h File
#property (nonatomic,retain) NSDictionary *dict;
.m file
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *rootPath;
rootPath = [[NSBundle mainBundle] pathForResource:#"FILE_NAME"
ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:rootPath];
dict = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
//NSLog(#"PLIST retrive:%#",plistXML);
if (!dictLang) {
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
Get Value ::
NSLog(#" Success :: %#", [dict objectForKey:#"success"]);
Hope, it'll help you.
Thanks.
if ([[NSPropertyListSerialization propertyListWithData:resultData options: NSPropertyListMutableContainersAndLeaves format:NULL error:nil] isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *tempDicts = [NSPropertyListSerialization propertyListWithData:nil options: NSPropertyListMutableContainersAndLeaves format:NULL error:nil];
}
else if ([[NSPropertyListSerialization propertyListWithData:resultData options: NSPropertyListMutableContainersAndLeaves format:NULL error:nil] isKindOfClass:[NSArray class]])
{
NSMutableArray *tempDicts = [NSPropertyListSerialization propertyListWithData:nil options: NSPropertyListMutableContainersAndLeaves format:NULL error:nil];
}
The return value of the method on NSPropertyListSerialization is - quite logically - the root object of the property list you receive. So result is an NSDictionary (I can't even imagine why you assume it's an array...!?). Try this:
NSLog(#"%#", [result objectForKey:#"success"]);

Unrecognized selector error when indexing into a dictionary of arrays

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..

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.

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