Skip to content

Commit

Permalink
Added convenience categories to create deeply mutable copies of colle…
Browse files Browse the repository at this point in the history
…ctions. This is very useful when applying patches to (non-mutable) collections.
  • Loading branch information
grgcombs committed Apr 15, 2014
1 parent 0edad3d commit 79094cd
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 7 deletions.
26 changes: 26 additions & 0 deletions JSONTools.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
37BDF4F718FB18EE00BF9705 /* JSONPatchDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 37BDF4F618FB18EE00BF9705 /* JSONPatchDictionary.m */; };
37BDF4FA18FB29AA00BF9705 /* JSONPatchArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 37BDF4F918FB29AA00BF9705 /* JSONPatchArray.m */; };
37BDF4FE18FB74C500BF9705 /* JSONPatchInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 37BDF4FD18FB74C500BF9705 /* JSONPatchInfo.m */; };
37F843B118FCE674003B20CC /* NSArray+JSONDeepMutable.m in Sources */ = {isa = PBXBuildFile; fileRef = 37F843B018FCE674003B20CC /* NSArray+JSONDeepMutable.m */; };
37F843B418FCE68C003B20CC /* NSDictionary+JSONDeepMutable.m in Sources */ = {isa = PBXBuildFile; fileRef = 37F843B318FCE68C003B20CC /* NSDictionary+JSONDeepMutable.m */; };
37F843B718FCE9B7003B20CC /* JSONDeeplyMutable.m in Sources */ = {isa = PBXBuildFile; fileRef = 37F843B618FCE9B6003B20CC /* JSONDeeplyMutable.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -47,6 +50,12 @@
37BDF4F918FB29AA00BF9705 /* JSONPatchArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatchArray.m; sourceTree = "<group>"; };
37BDF4FC18FB74C500BF9705 /* JSONPatchInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONPatchInfo.h; sourceTree = "<group>"; };
37BDF4FD18FB74C500BF9705 /* JSONPatchInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatchInfo.m; sourceTree = "<group>"; };
37F843AF18FCE674003B20CC /* NSArray+JSONDeepMutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+JSONDeepMutable.h"; sourceTree = "<group>"; };
37F843B018FCE674003B20CC /* NSArray+JSONDeepMutable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+JSONDeepMutable.m"; sourceTree = "<group>"; };
37F843B218FCE68C003B20CC /* NSDictionary+JSONDeepMutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+JSONDeepMutable.h"; sourceTree = "<group>"; };
37F843B318FCE68C003B20CC /* NSDictionary+JSONDeepMutable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+JSONDeepMutable.m"; sourceTree = "<group>"; };
37F843B518FCE94A003B20CC /* JSONDeeplyMutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONDeeplyMutable.h; sourceTree = "<group>"; };
37F843B618FCE9B6003B20CC /* JSONDeeplyMutable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONDeeplyMutable.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -125,6 +134,7 @@
37BDF4C518FAF1DD00BF9705 /* JSONTools.h */,
37A5005018FC7D5200A15B0F /* JSONPatch */,
37A5005118FC7D5F00A15B0F /* JSONPointer */,
37F843AE18FCE5C6003B20CC /* Utilities */,
37BDF4C318FAF1DD00BF9705 /* Supporting Files */,
);
path = JSONTools;
Expand Down Expand Up @@ -157,6 +167,19 @@
name = "Supporting Files";
sourceTree = "<group>";
};
37F843AE18FCE5C6003B20CC /* Utilities */ = {
isa = PBXGroup;
children = (
37F843B518FCE94A003B20CC /* JSONDeeplyMutable.h */,
37F843B618FCE9B6003B20CC /* JSONDeeplyMutable.m */,
37F843AF18FCE674003B20CC /* NSArray+JSONDeepMutable.h */,
37F843B018FCE674003B20CC /* NSArray+JSONDeepMutable.m */,
37F843B218FCE68C003B20CC /* NSDictionary+JSONDeepMutable.h */,
37F843B318FCE68C003B20CC /* NSDictionary+JSONDeepMutable.m */,
);
name = Utilities;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -227,6 +250,9 @@
37A5004F18FC3FC700A15B0F /* JSONPatchApplyTests.m in Sources */,
37BDF4ED18FB121A00BF9705 /* NSDictionary+JSONPointer.m in Sources */,
37A5004D18FB9FB200A15B0F /* JSONPointer.m in Sources */,
37F843B118FCE674003B20CC /* NSArray+JSONDeepMutable.m in Sources */,
37F843B718FCE9B7003B20CC /* JSONDeeplyMutable.m in Sources */,
37F843B418FCE68C003B20CC /* NSDictionary+JSONDeepMutable.m in Sources */,
37A5004A18FB9E7900A15B0F /* NSArray+JSONPointer.m in Sources */,
37BDF4DD18FAF1DD00BF9705 /* JSONPointerTests.m in Sources */,
);
Expand Down
28 changes: 28 additions & 0 deletions JSONTools/JSONDeeplyMutable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// JSONDeeplyMutable.h
// JSONTools
//
// Copyright (C) 2014 Gregory Combs [gcombs at gmail]
// See LICENSE.txt for details.

@import Foundation;
#import "NSArray+JSONDeepMutable.h"
#import "NSDictionary+JSONDeepMutable.h"

@interface JSONDeepMutable : NSObject

/**
* @private
* Use the NSArray+JSONDeepMutable and NSDictionary+JSONDeepMutable
* categories instead. This is an intern implementation that only operates
* on one instance of a container's content. Throws an exception if any
* interior value objects aren't copyable in some way. This method
* prefers NSMutableCopying over NSCopying whenever possible.
*
* @param oldValue A value object to (mutably) copy.
*
* @return A (mutable) copy of the object.
*/
+ (id)copyAsDeeplyMutableValue:(id)oldValue;

@end
37 changes: 37 additions & 0 deletions JSONTools/JSONDeeplyMutable.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// JSONDeeplyMutable.m
// JSONTools
//
// Copyright (C) 2014 Gregory Combs [gcombs at gmail]
// See LICENSE.txt for details.

#import "JSONDeeplyMutable.h"

@implementation JSONDeepMutable

+ (id)copyAsDeeplyMutableValue:(id)oldValue
{
id newCopy = nil;

if ([oldValue respondsToSelector: @selector(copyAsDeeplyMutableJSON)])
{
newCopy = [oldValue copyAsDeeplyMutableJSON];
}
else if ([oldValue conformsToProtocol:@protocol(NSMutableCopying)])
{
newCopy = [oldValue mutableCopy];
}
else if ([oldValue conformsToProtocol:@protocol(NSCopying)])
{
newCopy = [oldValue copy];
}

if (!newCopy)
{
[NSException raise:NSDestinationInvalidException format:@"Object is not mutable or copyable: %@", oldValue];
}

return newCopy;
}

@end
21 changes: 21 additions & 0 deletions JSONTools/NSArray+JSONDeepMutable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// NSArray+JSONDeepMutable.h
// JSONTools
//
// Copyright (C) 2014 Gregory Combs [gcombs at gmail]
// See LICENSE.txt for details.

@import Foundation;

@interface NSArray (JSONDeepMutable)

/**
* Recurses into the receiver's contents and makes a (mutable) copy of each value it encounters.
* Throws an exception if any interior value objects aren't copyable in some way. This method
* prefers NSMutableCopying over NSCopying whenever possible.
*
* @return A deeply mutable copy of the receiver's contents.
*/
- (NSMutableArray *)copyAsDeeplyMutableJSON;

@end
27 changes: 27 additions & 0 deletions JSONTools/NSArray+JSONDeepMutable.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// NSArray+JSONDeepMutable.m
// JSONTools
//
// Copyright (C) 2014 Gregory Combs [gcombs at gmail]
// See LICENSE.txt for details.

#import "NSArray+JSONDeepMutable.h"
#import "JSONDeeplyMutable.h"

@implementation NSArray (JSONDeepMutable)

- (NSMutableArray *)copyAsDeeplyMutableJSON
{
NSMutableArray* ret = [[NSMutableArray alloc] initWithCapacity: [self count]];
for (id oldValue in self)
{
id newCopy = [JSONDeepMutable copyAsDeeplyMutableValue:oldValue];
if (newCopy)
{
[ret addObject:newCopy];
}
}
return ret;
}

@end
21 changes: 21 additions & 0 deletions JSONTools/NSDictionary+JSONDeepMutable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// NSDictionary+JSONDeepMutable.h
// JSONTools
//
// Copyright (C) 2014 Gregory Combs [gcombs at gmail]
// See LICENSE.txt for details.

@import Foundation;

@interface NSDictionary (JSONDeepMutable)

/**
* Recurses into the receiver's contents and makes a (mutable) copy of each value it encounters.
* Throws an exception if any interior value objects aren't copyable in some way. This method
* prefers NSMutableCopying over NSCopying whenever possible.
*
* @return A deeply mutable copy of the receiver's contents.
*/
- (NSMutableDictionary *)copyAsDeeplyMutableJSON;

@end
28 changes: 28 additions & 0 deletions JSONTools/NSDictionary+JSONDeepMutable.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// NSDictionary+JSONDeepMutable.m
// JSONTools
//
// Copyright (C) 2014 Gregory Combs [gcombs at gmail]
// See LICENSE.txt for details.

#import "NSDictionary+JSONDeepMutable.h"
#import "JSONDeeplyMutable.h"

@implementation NSDictionary (JSONDeepMutable)

- (NSMutableDictionary *)copyAsDeeplyMutableJSON
{
NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity:[self count]];

[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
id newCopy = [JSONDeepMutable copyAsDeeplyMutableValue:obj];
if (newCopy)
{
ret[key] = newCopy;
}
}];

return ret;
}

@end
15 changes: 8 additions & 7 deletions JSONToolsTests/JSONPatchApplyTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

@import XCTest;
#import "JSONPatch.h"
#import "JSONDeeplyMutable.h"

@interface JSONPatchApplyTests : XCTestCase

Expand All @@ -29,7 +30,7 @@ - (void)tearDown
- (void)testShouldApplyAdd
{
NSMutableDictionary *initial = [@{@"foo": @1,
@"baz": [@[@{@"qux": @"hello"}] mutableCopy]} mutableCopy];
@"baz": @[@{@"qux": @"hello"}]} copyAsDeeplyMutableJSON];
NSMutableDictionary *obj = [initial mutableCopy];
NSMutableDictionary *expected = [initial mutableCopy];

Expand Down Expand Up @@ -78,8 +79,8 @@ - (void)testShouldApplyAdd
- (void)testShouldApplyRemove
{
NSMutableDictionary *obj = [@{@"foo": @1,
@"baz": @[[@{@"qux": @"hello"} mutableCopy]],
@"bar": @[@1,@2,@3,@4]} mutableCopy];
@"baz": @[@{@"qux": @"hello"}],
@"bar": @[@1,@2,@3,@4]} copyAsDeeplyMutableJSON];

NSMutableDictionary *expected = [obj mutableCopy];
[expected removeObjectForKey:@"bar"];
Expand All @@ -88,7 +89,7 @@ - (void)testShouldApplyRemove
XCTAssertEqualObjects(obj, expected, @"Failed to apply remove patch, expected %@, found %@", expected, obj);

expected = [@{@"foo": @1,
@"baz": @[[@{} mutableCopy]]} mutableCopy];
@"baz": @[@{}]} copyAsDeeplyMutableJSON];
[JSONPatch applyPatches:@[@{@"op": @"remove",
@"path": @"/baz/0/qux"}] toCollection:obj];
XCTAssertEqualObjects(obj, expected, @"Failed to apply remove patch, expected %@, found %@", expected, obj);
Expand All @@ -97,7 +98,7 @@ - (void)testShouldApplyRemove
- (void)testShouldApplyReplace
{
NSMutableDictionary *obj = [@{@"foo": @1,
@"baz": @[[@{@"qux": @"hello"} mutableCopy]]} mutableCopy];
@"baz": @[@{@"qux": @"hello"}]} copyAsDeeplyMutableJSON];

NSMutableDictionary *expected = [obj mutableCopy];
expected[@"foo"] = @[@1,@2,@3,@4];
Expand Down Expand Up @@ -133,7 +134,7 @@ - (void)testShouldApplyTest
- (void)testShouldApplyMove
{
NSMutableDictionary *obj = [@{@"foo": @1,
@"baz": [@[[@{@"qux": @"hello"} mutableCopy]] mutableCopy]} mutableCopy];
@"baz": @[@{@"qux": @"hello"}]} copyAsDeeplyMutableJSON];
NSMutableDictionary *expected = [obj mutableCopy];

expected[@"bar"] = @1;
Expand All @@ -154,7 +155,7 @@ - (void)testShouldApplyMove
- (void)testShouldApplyCopy
{
NSMutableDictionary *obj = [@{@"foo": @1,
@"baz": [@[[@{@"qux": @"hello"} mutableCopy]] mutableCopy]} mutableCopy];
@"baz": @[@{@"qux": @"hello"}]} copyAsDeeplyMutableJSON];
NSMutableDictionary *expected = [obj mutableCopy];

expected[@"bar"] = @1;
Expand Down

0 comments on commit 79094cd

Please sign in to comment.