diff --git a/.gitignore b/.gitignore index d522f94..85ce182 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,26 @@ # # Pods/ +# Xcode +.DS_Store +build/* +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +#*.xcworkspace +!default.xcworkspace +xcuserdata +profile +*.moved-aside +.svn +/Builds +xcuserdata/* +/DerivedData +build +*.orig +*.xccheckout diff --git a/JSONTools.xcodeproj/project.pbxproj b/JSONTools.xcodeproj/project.pbxproj new file mode 100644 index 0000000..56ba990 --- /dev/null +++ b/JSONTools.xcodeproj/project.pbxproj @@ -0,0 +1,377 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 37A5004A18FB9E7900A15B0F /* NSArray+JSONPointer.m in Sources */ = {isa = PBXBuildFile; fileRef = 37A5004918FB9E7900A15B0F /* NSArray+JSONPointer.m */; }; + 37A5004D18FB9FB200A15B0F /* JSONPointer.m in Sources */ = {isa = PBXBuildFile; fileRef = 37A5004C18FB9FB200A15B0F /* JSONPointer.m */; }; + 37A5004F18FC3FC700A15B0F /* JSONPatchApplyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 37A5004E18FC3FC700A15B0F /* JSONPatchApplyTests.m */; }; + 37BDF4CF18FAF1DD00BF9705 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37BDF4CE18FAF1DD00BF9705 /* XCTest.framework */; }; + 37BDF4D018FAF1DD00BF9705 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37BDF4C018FAF1DD00BF9705 /* Foundation.framework */; }; + 37BDF4D218FAF1DD00BF9705 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37BDF4D118FAF1DD00BF9705 /* UIKit.framework */; }; + 37BDF4DB18FAF1DD00BF9705 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 37BDF4D918FAF1DD00BF9705 /* InfoPlist.strings */; }; + 37BDF4DD18FAF1DD00BF9705 /* JSONPointerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 37BDF4DC18FAF1DD00BF9705 /* JSONPointerTests.m */; }; + 37BDF4ED18FB121A00BF9705 /* NSDictionary+JSONPointer.m in Sources */ = {isa = PBXBuildFile; fileRef = 37BDF4EA18FAF5FD00BF9705 /* NSDictionary+JSONPointer.m */; }; + 37BDF4F118FB15C300BF9705 /* JSONPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 37BDF4F018FB15C300BF9705 /* JSONPatch.m */; }; + 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 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 37A5004818FB9E7900A15B0F /* NSArray+JSONPointer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+JSONPointer.h"; sourceTree = ""; }; + 37A5004918FB9E7900A15B0F /* NSArray+JSONPointer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+JSONPointer.m"; sourceTree = ""; }; + 37A5004B18FB9FB200A15B0F /* JSONPointer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONPointer.h; sourceTree = ""; }; + 37A5004C18FB9FB200A15B0F /* JSONPointer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPointer.m; sourceTree = ""; }; + 37A5004E18FC3FC700A15B0F /* JSONPatchApplyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatchApplyTests.m; sourceTree = ""; }; + 37BDF4C018FAF1DD00BF9705 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 37BDF4C418FAF1DD00BF9705 /* JSONTools-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JSONTools-Prefix.pch"; sourceTree = ""; }; + 37BDF4C518FAF1DD00BF9705 /* JSONTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSONTools.h; sourceTree = ""; }; + 37BDF4CD18FAF1DD00BF9705 /* JSONToolsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSONToolsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 37BDF4CE18FAF1DD00BF9705 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 37BDF4D118FAF1DD00BF9705 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 37BDF4D818FAF1DD00BF9705 /* JSONToolsTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JSONToolsTests-Info.plist"; sourceTree = ""; }; + 37BDF4DA18FAF1DD00BF9705 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 37BDF4DC18FAF1DD00BF9705 /* JSONPointerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JSONPointerTests.m; sourceTree = ""; }; + 37BDF4E918FAF5FD00BF9705 /* NSDictionary+JSONPointer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+JSONPointer.h"; sourceTree = ""; }; + 37BDF4EA18FAF5FD00BF9705 /* NSDictionary+JSONPointer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+JSONPointer.m"; sourceTree = ""; }; + 37BDF4EF18FB15C300BF9705 /* JSONPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONPatch.h; sourceTree = ""; }; + 37BDF4F018FB15C300BF9705 /* JSONPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatch.m; sourceTree = ""; }; + 37BDF4F518FB18EE00BF9705 /* JSONPatchDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONPatchDictionary.h; sourceTree = ""; }; + 37BDF4F618FB18EE00BF9705 /* JSONPatchDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatchDictionary.m; sourceTree = ""; }; + 37BDF4F818FB29AA00BF9705 /* JSONPatchArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONPatchArray.h; sourceTree = ""; }; + 37BDF4F918FB29AA00BF9705 /* JSONPatchArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatchArray.m; sourceTree = ""; }; + 37BDF4FC18FB74C500BF9705 /* JSONPatchInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONPatchInfo.h; sourceTree = ""; }; + 37BDF4FD18FB74C500BF9705 /* JSONPatchInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONPatchInfo.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 37BDF4CA18FAF1DD00BF9705 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 37BDF4CF18FAF1DD00BF9705 /* XCTest.framework in Frameworks */, + 37BDF4D218FAF1DD00BF9705 /* UIKit.framework in Frameworks */, + 37BDF4D018FAF1DD00BF9705 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 37A5005018FC7D5200A15B0F /* JSONPatch */ = { + isa = PBXGroup; + children = ( + 37BDF4EF18FB15C300BF9705 /* JSONPatch.h */, + 37BDF4F018FB15C300BF9705 /* JSONPatch.m */, + 37BDF4FC18FB74C500BF9705 /* JSONPatchInfo.h */, + 37BDF4FD18FB74C500BF9705 /* JSONPatchInfo.m */, + 37BDF4F518FB18EE00BF9705 /* JSONPatchDictionary.h */, + 37BDF4F618FB18EE00BF9705 /* JSONPatchDictionary.m */, + 37BDF4F818FB29AA00BF9705 /* JSONPatchArray.h */, + 37BDF4F918FB29AA00BF9705 /* JSONPatchArray.m */, + ); + name = JSONPatch; + sourceTree = ""; + }; + 37A5005118FC7D5F00A15B0F /* JSONPointer */ = { + isa = PBXGroup; + children = ( + 37A5004B18FB9FB200A15B0F /* JSONPointer.h */, + 37A5004C18FB9FB200A15B0F /* JSONPointer.m */, + 37BDF4E918FAF5FD00BF9705 /* NSDictionary+JSONPointer.h */, + 37BDF4EA18FAF5FD00BF9705 /* NSDictionary+JSONPointer.m */, + 37A5004818FB9E7900A15B0F /* NSArray+JSONPointer.h */, + 37A5004918FB9E7900A15B0F /* NSArray+JSONPointer.m */, + ); + name = JSONPointer; + sourceTree = ""; + }; + 37BDF4B418FAF1DD00BF9705 = { + isa = PBXGroup; + children = ( + 37BDF4C218FAF1DD00BF9705 /* JSONTools */, + 37BDF4D618FAF1DD00BF9705 /* JSONToolsTests */, + 37BDF4BF18FAF1DD00BF9705 /* Frameworks */, + 37BDF4BE18FAF1DD00BF9705 /* Products */, + ); + sourceTree = ""; + }; + 37BDF4BE18FAF1DD00BF9705 /* Products */ = { + isa = PBXGroup; + children = ( + 37BDF4CD18FAF1DD00BF9705 /* JSONToolsTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 37BDF4BF18FAF1DD00BF9705 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 37BDF4C018FAF1DD00BF9705 /* Foundation.framework */, + 37BDF4CE18FAF1DD00BF9705 /* XCTest.framework */, + 37BDF4D118FAF1DD00BF9705 /* UIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 37BDF4C218FAF1DD00BF9705 /* JSONTools */ = { + isa = PBXGroup; + children = ( + 37BDF4C518FAF1DD00BF9705 /* JSONTools.h */, + 37A5005018FC7D5200A15B0F /* JSONPatch */, + 37A5005118FC7D5F00A15B0F /* JSONPointer */, + 37BDF4C318FAF1DD00BF9705 /* Supporting Files */, + ); + path = JSONTools; + sourceTree = ""; + }; + 37BDF4C318FAF1DD00BF9705 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 37BDF4C418FAF1DD00BF9705 /* JSONTools-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 37BDF4D618FAF1DD00BF9705 /* JSONToolsTests */ = { + isa = PBXGroup; + children = ( + 37BDF4DC18FAF1DD00BF9705 /* JSONPointerTests.m */, + 37A5004E18FC3FC700A15B0F /* JSONPatchApplyTests.m */, + 37BDF4D718FAF1DD00BF9705 /* Supporting Files */, + ); + path = JSONToolsTests; + sourceTree = ""; + }; + 37BDF4D718FAF1DD00BF9705 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 37BDF4D818FAF1DD00BF9705 /* JSONToolsTests-Info.plist */, + 37BDF4D918FAF1DD00BF9705 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 37BDF4CC18FAF1DD00BF9705 /* JSONToolsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 37BDF4E318FAF1DD00BF9705 /* Build configuration list for PBXNativeTarget "JSONToolsTests" */; + buildPhases = ( + 37BDF4C918FAF1DD00BF9705 /* Sources */, + 37BDF4CA18FAF1DD00BF9705 /* Frameworks */, + 37BDF4CB18FAF1DD00BF9705 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = JSONToolsTests; + productName = JSONToolsTests; + productReference = 37BDF4CD18FAF1DD00BF9705 /* JSONToolsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 37BDF4B518FAF1DD00BF9705 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = JSON; + LastUpgradeCheck = 0510; + ORGANIZATIONNAME = Sleestacks; + }; + buildConfigurationList = 37BDF4B818FAF1DD00BF9705 /* Build configuration list for PBXProject "JSONTools" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 37BDF4B418FAF1DD00BF9705; + productRefGroup = 37BDF4BE18FAF1DD00BF9705 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 37BDF4CC18FAF1DD00BF9705 /* JSONToolsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 37BDF4CB18FAF1DD00BF9705 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 37BDF4DB18FAF1DD00BF9705 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 37BDF4C918FAF1DD00BF9705 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 37BDF4F118FB15C300BF9705 /* JSONPatch.m in Sources */, + 37BDF4FA18FB29AA00BF9705 /* JSONPatchArray.m in Sources */, + 37BDF4FE18FB74C500BF9705 /* JSONPatchInfo.m in Sources */, + 37BDF4F718FB18EE00BF9705 /* JSONPatchDictionary.m in Sources */, + 37A5004F18FC3FC700A15B0F /* JSONPatchApplyTests.m in Sources */, + 37BDF4ED18FB121A00BF9705 /* NSDictionary+JSONPointer.m in Sources */, + 37A5004D18FB9FB200A15B0F /* JSONPointer.m in Sources */, + 37A5004A18FB9E7900A15B0F /* NSArray+JSONPointer.m in Sources */, + 37BDF4DD18FAF1DD00BF9705 /* JSONPointerTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 37BDF4D918FAF1DD00BF9705 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 37BDF4DA18FAF1DD00BF9705 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 37BDF4DE18FAF1DD00BF9705 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 37BDF4DF18FAF1DD00BF9705 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 37BDF4E418FAF1DD00BF9705 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "JSONTools/JSONTools-Prefix.pch"; + INFOPLIST_FILE = "JSONToolsTests/JSONToolsTests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + 37BDF4E518FAF1DD00BF9705 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "JSONTools/JSONTools-Prefix.pch"; + INFOPLIST_FILE = "JSONToolsTests/JSONToolsTests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 37BDF4B818FAF1DD00BF9705 /* Build configuration list for PBXProject "JSONTools" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 37BDF4DE18FAF1DD00BF9705 /* Debug */, + 37BDF4DF18FAF1DD00BF9705 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 37BDF4E318FAF1DD00BF9705 /* Build configuration list for PBXNativeTarget "JSONToolsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 37BDF4E418FAF1DD00BF9705 /* Debug */, + 37BDF4E518FAF1DD00BF9705 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 37BDF4B518FAF1DD00BF9705 /* Project object */; +} diff --git a/JSONTools.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/JSONTools.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..fcd0573 --- /dev/null +++ b/JSONTools.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/JSONTools.xcodeproj/xcuserdata/greg.xcuserdatad/xcschemes/JSONTools.xcscheme b/JSONTools.xcodeproj/xcuserdata/greg.xcuserdatad/xcschemes/JSONTools.xcscheme new file mode 100644 index 0000000..4a51504 --- /dev/null +++ b/JSONTools.xcodeproj/xcuserdata/greg.xcuserdatad/xcschemes/JSONTools.xcscheme @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JSONTools.xcodeproj/xcuserdata/greg.xcuserdatad/xcschemes/xcschememanagement.plist b/JSONTools.xcodeproj/xcuserdata/greg.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..50f6c73 --- /dev/null +++ b/JSONTools.xcodeproj/xcuserdata/greg.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + JSONTools.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 37BDF4BC18FAF1DD00BF9705 + + primary + + + 37BDF4CC18FAF1DD00BF9705 + + primary + + + + + diff --git a/JSONTools/JSONPatch.h b/JSONTools/JSONPatch.h new file mode 100644 index 0000000..ee19c39 --- /dev/null +++ b/JSONTools/JSONPatch.h @@ -0,0 +1,32 @@ +// +// JSONPatch.h +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONTools.h" + +@interface JSONPatch : NSObject + +/** + * Implements IETF RFC6902 - JSON Patch + * @see https://tools.ietf.org/html/rfc6902 + * + * @param patches An array of one or more patch dictionaries in the form of: + * `{"op":"add", "path": "/foo/0/bar", "value": "thing"}` + * - `op` is one of: "add", "remove", "copy", "move", "test", "_get" + * - `path` is a JSON Pointer (RFC 6901) (see JSONPointer.h) + * - `value` is an objective-c object ( + * + * @param collection A ***mutable*** dictionary or ***mutable*** array to patch + * + * @return For all but "_get", the result is an NSNumber boolean indicating patch success. + * However, for "_get" operations, the result will be the collection's content + * corresponding to the supplied patch JSON Pointer (i.e. "path") + */ ++ (id)applyPatches:(NSArray *)patches toCollection:(id)collection; + +@end diff --git a/JSONTools/JSONPatch.m b/JSONTools/JSONPatch.m new file mode 100644 index 0000000..47bf069 --- /dev/null +++ b/JSONTools/JSONPatch.m @@ -0,0 +1,179 @@ +// +// JSONPatch.m +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONPatch.h" +#import "JSONPatchDictionary.h" +#import "JSONPatchArray.h" +#import "JSONPointer.h" + +@implementation JSONPatch + ++ (id)applyPatches:(NSArray *)patches toCollection:(id)collection +{ + if (!collection || + ![collection respondsToSelector:@selector(mutableCopy)]) + { + return nil; + } + + id result = nil; + + for (NSDictionary *patch in patches) + { + JSONPatchInfo *patchInfo = [JSONPatchInfo newPatchInfoWithDictionary:patch]; + if (!patchInfo) + break; + + NSArray *pathKeys = [patchInfo.path componentsSeparatedByString:@"/"]; + id object = collection; + NSInteger t = 1; + + while (YES) { + if (![object isKindOfClass:[NSMutableDictionary class]] && + ![object isKindOfClass:[NSMutableArray class]] && + [object respondsToSelector:@selector(mutableCopy)]) + { + object = [object mutableCopy]; + } + + if ([object isKindOfClass:[NSMutableArray class]]) + { + NSString *component = pathKeys[t]; + NSInteger index = [(NSMutableArray *)object indexForJSONPointerComponent:component]; + if (index == NSNotFound) + break; + + t++; + + if (t >= pathKeys.count) + { + BOOL stop = NO; + result = [self applyPatch:patchInfo array:object index:index collection:collection stop:&stop]; + if (stop) { + return result; + } + break; + } + object = (NSMutableArray *)object[index]; + } + else if ([object isKindOfClass:[NSMutableDictionary class]]) + { + NSString *component = [(NSMutableDictionary *)object keyForJSONPointerComponent:pathKeys[t]]; + + t++; + + if (t >= pathKeys.count) + { + BOOL stop = NO; + result = [self applyPatch:patchInfo dictionary:object key:component collection:collection stop:&stop]; + if (stop) { + return result; + } + break; + } + object = (NSMutableDictionary *)object[component]; + } + } + } + return result; +} + +#pragma mark - Singular Patch + ++ (id)applyPatch:(JSONPatchInfo *)patchInfo array:(NSMutableArray *)array index:(NSInteger)index collection:(id)collection stop:(BOOL *)stop +{ + BOOL success = NO; + switch (patchInfo.op) + { + case JSONPatchOperationGet: + case JSONPatchOperationTest: + *stop = YES; + return [JSONPatchArray applyPatchInfo:patchInfo object:array index:index]; + break; + case JSONPatchOperationAdd: + case JSONPatchOperationReplace: + case JSONPatchOperationRemove: + success = ([[JSONPatchArray applyPatchInfo:patchInfo object:array index:index] boolValue]); + break; + case JSONPatchOperationCopy: + success = [self applyCopyPatch:patchInfo toCollection:collection]; + break; + case JSONPatchOperationMove: + success = [self applyMovePatch:patchInfo toCollection:collection]; + break; + case JSONPatchOperationUndefined: + break; + } + if (!success) + return nil; + return @(success); +} + ++ (id)applyPatch:(JSONPatchInfo *)patchInfo dictionary:(NSMutableDictionary *)dictionary key:(NSString *)key collection:(id)collection stop:(BOOL *)stop +{ + BOOL success = NO; + switch (patchInfo.op) + { + case JSONPatchOperationGet: + case JSONPatchOperationTest: + *stop = YES; + return [JSONPatchDictionary applyPatchInfo:patchInfo object:dictionary key:key]; + break; + case JSONPatchOperationAdd: + case JSONPatchOperationReplace: + case JSONPatchOperationRemove: + success = ([[JSONPatchDictionary applyPatchInfo:patchInfo object:dictionary key:key] boolValue]); + break; + case JSONPatchOperationCopy: + success = [self applyCopyPatch:patchInfo toCollection:collection]; + break; + case JSONPatchOperationMove: + success = [self applyMovePatch:patchInfo toCollection:collection]; + break; + case JSONPatchOperationUndefined: + break; + } + if (!success) + return nil; + return @(success); +} + +#pragma mark - Aggregated Operations + ++ (BOOL)applyCopyPatch:(JSONPatchInfo *)patchInfo toCollection:(id)collection +{ + id fromValue = [self applyPatches:@[@{@"op": @"_get", + @"path": patchInfo.fromPath}] toCollection:collection]; + if (!fromValue) { + return NO; + } + id toResult = [self applyPatches:@[@{@"op": @"add", + @"path": patchInfo.path, + @"value": fromValue}] toCollection:collection]; + return (toResult != NULL); +} + ++ (BOOL)applyMovePatch:(JSONPatchInfo *)patchInfo toCollection:(id)collection +{ + id fromValue = [self applyPatches:@[@{@"op": @"_get", + @"path": patchInfo.fromPath}] toCollection:collection]; + if (!fromValue) + { + return NO; + } + id removeResult = [self applyPatches:@[@{@"op": @"remove", + @"path": patchInfo.fromPath}] toCollection:collection]; + id toResult = [self applyPatches:@[@{@"op": @"add", + @"path": patchInfo.path, + @"value": fromValue}] toCollection:collection]; + return (toResult != NULL && + removeResult != NULL); +} + +@end diff --git a/JSONTools/JSONPatchArray.h b/JSONTools/JSONPatchArray.h new file mode 100644 index 0000000..41ca57b --- /dev/null +++ b/JSONTools/JSONPatchArray.h @@ -0,0 +1,15 @@ +// +// JSONPatchArray.h +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONTools.h" +#import "JSONPatchInfo.h" + +@interface JSONPatchArray: NSObject ++ (id)applyPatchInfo:(JSONPatchInfo *)info object:(NSMutableArray *)object index:(NSInteger)index; +@end diff --git a/JSONTools/JSONPatchArray.m b/JSONTools/JSONPatchArray.m new file mode 100644 index 0000000..df7c635 --- /dev/null +++ b/JSONTools/JSONPatchArray.m @@ -0,0 +1,114 @@ +// +// JSONPatchArray.m +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONPatchArray.h" +#import "NSArray+JSONPointer.h" + +@implementation JSONPatchArray + ++ (id)applyPatchInfo:(JSONPatchInfo *)info object:(NSMutableArray *)object index:(NSInteger)index +{ + BOOL success = NO; + switch (info.op) + { + case JSONPatchOperationGet: + return [self getValueForObject:object index:index]; + break; + case JSONPatchOperationTest: + return @([self test:object index:index value:info.value]); + break; + case JSONPatchOperationAdd: + success = [self addObject:object index:index value:info.value]; + break; + case JSONPatchOperationReplace: + success = [self replaceObject:object index:index value:info.value]; + break; + case JSONPatchOperationRemove: + success = [self removeObject:object index:index]; + break; + case JSONPatchOperationMove: + case JSONPatchOperationCopy: + case JSONPatchOperationUndefined: + // These are handled in the main patch loop + break; + } + if (!success) + { + return nil; + } + return @(success); +} + ++ (BOOL)isArray:(NSArray *)array andIndex:(NSInteger)index inclusive:(BOOL)isInclusive +{ + if (!array || + ![array isKindOfClass:[NSArray class]]) + { + return NO; + } + if (isInclusive) { + return (array.count >= index); + } + return (array.count > index); +} + ++ (BOOL)test:(NSArray *)object index:(NSInteger)index value:(id)value +{ + if (![self isArray:object andIndex:index inclusive:NO]) + { + return NO; + } + id foundValue = object[index]; + return (foundValue == value || [foundValue isEqual:value]); +} + ++ (id)getValueForObject:(NSArray *)object index:(NSInteger)index +{ + if (![self isArray:object andIndex:index inclusive:NO]) + { + return nil; + } + return object[index]; +} + ++ (BOOL)addObject:(NSMutableArray *)object index:(NSInteger)index value:(id)value +{ + if (![self isArray:object andIndex:index inclusive:YES] || + !value) + { + return NO; + } + [object insertObject:value atIndex:index]; + return YES; +} + ++ (BOOL)removeObject:(NSMutableArray *)object index:(NSInteger)index +{ + if (![self isArray:object andIndex:index inclusive:NO]) + { + return NO; + } + [object removeObjectAtIndex:index]; + return YES; +} + ++ (BOOL)replaceObject:(NSMutableArray *)object index:(NSInteger)index value:(id)value +{ + if (![self isArray:object andIndex:index inclusive:NO]) + { + return NO; + } + if (!value) + { + return [self removeObject:object index:index]; + } + return [self addObject:object index:index value:value]; +} + +@end diff --git a/JSONTools/JSONPatchDictionary.h b/JSONTools/JSONPatchDictionary.h new file mode 100644 index 0000000..a5c0086 --- /dev/null +++ b/JSONTools/JSONPatchDictionary.h @@ -0,0 +1,15 @@ +// +// JSONPatchDictionary.h +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONTools.h" +#import "JSONPatchInfo.h" + +@interface JSONPatchDictionary: NSObject ++ (id)applyPatchInfo:(JSONPatchInfo *)info object:(NSMutableDictionary *)object key:(NSString *)key; +@end diff --git a/JSONTools/JSONPatchDictionary.m b/JSONTools/JSONPatchDictionary.m new file mode 100644 index 0000000..3d92258 --- /dev/null +++ b/JSONTools/JSONPatchDictionary.m @@ -0,0 +1,116 @@ +// +// JSONPatchDictionary.m +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONPatchDictionary.h" +#import "JSONPointer.h" + +@implementation JSONPatchDictionary + ++ (id)applyPatchInfo:(JSONPatchInfo *)info object:(NSMutableDictionary *)object key:(NSString *)key +{ + BOOL success = NO; + switch (info.op) + { + case JSONPatchOperationGet: + return [self getValueForObject:object key:key]; + break; + case JSONPatchOperationTest: + return @([self test:object key:key value:info.value]); + break; + case JSONPatchOperationAdd: + success = [self addObject:object key:key value:info.value]; + break; + case JSONPatchOperationReplace: + success = [self replaceObject:object key:key value:info.value]; + break; + case JSONPatchOperationRemove: + success = [self removeObject:object key:key]; + break; + case JSONPatchOperationMove: + case JSONPatchOperationCopy: + case JSONPatchOperationUndefined: + // These are handled in the main patch loop + break; + } + if (!success) + { + return nil; + } + return @(success); +} + ++ (BOOL)isDictionary:(NSDictionary *)dictionary andString:(NSString *)string +{ + if (!string || + ![string isKindOfClass:[NSString class]] || + !dictionary || + ![dictionary isKindOfClass:[NSDictionary class]]) + { + return NO; + } + return YES; +} + ++ (BOOL)test:(NSDictionary *)object key:(NSString *)key value:(id)value +{ + if (![self isDictionary:object andString:key]) + { + return NO; + } + id foundValue = object[key]; + return (foundValue == value || [foundValue isEqual:value]); +} + ++ (id)getValueForObject:(NSDictionary *)object key:(NSString *)key +{ + if (![self isDictionary:object andString:key]) + { + return nil; + } + // this.value = object[key] ???? + return object[key]; +} + ++ (BOOL)addObject:(NSMutableDictionary *)object key:(NSString *)key value:(id)value +{ + if (![self isDictionary:object andString:key] || + !value) + { + return NO; + } + object[key] = value; + return YES; +} + ++ (BOOL)removeObject:(NSMutableDictionary *)object key:(NSString *)key +{ + if (![self isDictionary:object andString:key] || + !object[key]) + { + return NO; + } + [object removeObjectForKey:key]; + return YES; +} + ++ (BOOL)replaceObject:(NSMutableDictionary *)object key:(NSString *)key value:(id)value +{ + if (![self isDictionary:object andString:key] || + !object[key]) + { + return NO; + } + if (!value) + { + return [self removeObject:object key:key]; + } + return [self addObject:object key:key value:value]; +} + +@end diff --git a/JSONTools/JSONPatchInfo.h b/JSONTools/JSONPatchInfo.h new file mode 100644 index 0000000..ca0446f --- /dev/null +++ b/JSONTools/JSONPatchInfo.h @@ -0,0 +1,30 @@ +// +// JSONPatchInfo.h +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import Foundation; + +typedef NS_ENUM(NSInteger, JSONPatchOperation) +{ + JSONPatchOperationUndefined = -1, + JSONPatchOperationAdd, + JSONPatchOperationReplace, + JSONPatchOperationTest, + JSONPatchOperationRemove, + JSONPatchOperationMove, + JSONPatchOperationCopy, + JSONPatchOperationGet +}; + +@interface JSONPatchInfo : NSObject ++ (instancetype)newPatchInfoWithDictionary:(NSDictionary *)patch; +@property (nonatomic,copy,readonly) NSString *path; +@property (nonatomic,copy,readonly) NSString *fromPath; // for move/copy ops +@property (nonatomic,readonly) JSONPatchOperation op; +@property (nonatomic,strong,readonly) id value; +@end diff --git a/JSONTools/JSONPatchInfo.m b/JSONTools/JSONPatchInfo.m new file mode 100644 index 0000000..ec41955 --- /dev/null +++ b/JSONTools/JSONPatchInfo.m @@ -0,0 +1,198 @@ +// +// JSONPatchInfo.m +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONPatchInfo.h" + +@interface JSONPatchInfo () +@end + +@implementation JSONPatchInfo + ++ (instancetype)newPatchInfoWithDictionary:(NSDictionary *)patch +{ + if (!patch || + ![patch isKindOfClass:[NSDictionary class]]) + { + return nil; + } + + JSONPatchOperation patchOp = [self operationForToken:patch[@"op"]]; + if (patchOp == JSONPatchOperationUndefined) + { + return nil; + } + + JSONPatchInfo *info = [[JSONPatchInfo alloc] init]; + info->_op = patchOp; + + NSString *token = @"path"; + if ([self operation:patchOp needsToken:token]) + { + NSString *patchPath = patch[token]; + if (!patchPath || + ![patchPath isKindOfClass:[NSString class]]) + { + return nil; + } + info->_path = [patchPath copy]; + } + + token = @"from"; + if ([self operation:patchOp needsToken:token]) + { + NSString *fromPath = patch[token]; + if (!fromPath || + ![fromPath isKindOfClass:[NSString class]]) + { + return nil; + } + info->_fromPath = fromPath; + } + + token = @"value"; + if ([self operation:patchOp needsToken:token]) + { + id patchValue = patch[token]; + if (!patchValue) + return nil; + info->_value = patchValue; + } + + return info; +} + ++ (BOOL)operation:(JSONPatchOperation)op needsToken:(NSString *)token +{ + if ([token isEqualToString:@"path"] || + [token isEqualToString:@"op"]) + { + return (op != JSONPatchOperationUndefined); + } + + if ([token isEqualToString:@"from"]) + { + return (op == JSONPatchOperationMove || + op == JSONPatchOperationCopy); + } + + if ([token isEqualToString:@"value"]) + { + return (op == JSONPatchOperationAdd || + op == JSONPatchOperationReplace || + op == JSONPatchOperationTest); + } + + return NO; +} + ++ (JSONPatchOperation)operationForToken:(NSString *)token +{ + if (!token || ![token isKindOfClass:[NSString class]]) + return JSONPatchOperationUndefined; + if ([token caseInsensitiveCompare:@"add"] == NSOrderedSame) + return JSONPatchOperationAdd; + if ([token caseInsensitiveCompare:@"replace"] == NSOrderedSame) + return JSONPatchOperationReplace; + if ([token caseInsensitiveCompare:@"test"] == NSOrderedSame) + return JSONPatchOperationTest; + if ([token caseInsensitiveCompare:@"remove"] == NSOrderedSame) + return JSONPatchOperationRemove; + if ([token caseInsensitiveCompare:@"move"] == NSOrderedSame) + return JSONPatchOperationMove; + if ([token caseInsensitiveCompare:@"copy"] == NSOrderedSame) + return JSONPatchOperationCopy; + if ([token caseInsensitiveCompare:@"_get"] == NSOrderedSame) + return JSONPatchOperationGet; + return JSONPatchOperationUndefined; +} + ++ (NSString *)tokenForOperation:(JSONPatchOperation)operation +{ + NSString *token = nil; + + switch (operation) { + case JSONPatchOperationAdd: + token = @"add"; + break; + case JSONPatchOperationReplace: + token = @"replace"; + break; + case JSONPatchOperationTest: + token = @"test"; + break; + case JSONPatchOperationRemove: + token = @"remove"; + break; + case JSONPatchOperationMove: + token = @"move"; + break; + case JSONPatchOperationCopy: + token = @"copy"; + break; + case JSONPatchOperationGet: + token = @"_get"; + break; + case JSONPatchOperationUndefined: + token = @"undefined"; + break; + } + return token; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@: op=%@; path=%@; from=%@; value=%@", + [super description], + [[self class] tokenForOperation:_op], + _path, + _fromPath, + _value]; +} + +- (BOOL)isEqual:(id)obj +{ + if (![obj isKindOfClass:[JSONPatchInfo class]]) + return NO; + + JSONPatchInfo *other = (JSONPatchInfo *)obj; + BOOL equalOps = (_op == other->_op); + BOOL equalPaths = (_path == other->_path || [_path isEqual:other->_path]); + BOOL equalValues = (_value == other->_value || [_value isEqual:other->_value]); + BOOL equalFroms = (_fromPath == other->_fromPath || [_fromPath isEqual:other->_fromPath]); + + return (equalOps && + equalPaths && + equalValues && + equalFroms); +} + +#define NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger)) +#define NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (NSUINT_BIT - howmuch))) + +- (NSUInteger)hash +{ + NSUInteger current = 31; + current = [self hashForComponentOrNil:_op index:1] ^ current; + current = [self hashForComponentOrNil:[_path hash] index:2] ^ current; + current = [self hashForComponentOrNil:[_value hash] index:3] ^ current; + current = [self hashForComponentOrNil:[_fromPath hash] index:4] ^ current; + return current; +} + +- (NSUInteger)hashForComponentOrNil:(NSUInteger)hash index:(NSUInteger)hashIndex +{ + if (hash == 0) + { + // accounts for nil objects + hash = 31; + } + return NSUINTROTATE(hash, NSUINT_BIT / (hashIndex + 1)); +} + +@end diff --git a/JSONTools/JSONPointer.h b/JSONTools/JSONPointer.h new file mode 100644 index 0000000..804a4cb --- /dev/null +++ b/JSONTools/JSONPointer.h @@ -0,0 +1,31 @@ +// +// JSONPointer.h +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import Foundation; +#import "NSArray+JSONPointer.h" +#import "NSDictionary+JSONPointer.h" + +@interface JSONPointer : NSObject + +/** + * Implements IETF RFC6901 - JSON Pointer + * @see https://tools.ietf.org/html/rfc6901 + * + * The supplied collection (dictionary or array) returns a value corresponding to + * the supplied JSON Pointer reference. + * + * @param collection A collection (either dictionary or array). + * + * @param pointer A string in the form of a JSON Pointer, like "/foo/bar/0" or "#/foo" + * + * @return The pointer's corresponding content value (or nil) in the collection. + */ ++ (id)valueForCollection:(id)collection withJSONPointer:(NSString *)pointer; + +@end diff --git a/JSONTools/JSONPointer.m b/JSONTools/JSONPointer.m new file mode 100644 index 0000000..81a470a --- /dev/null +++ b/JSONTools/JSONPointer.m @@ -0,0 +1,105 @@ +// +// JSONPointer.m +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "JSONPointer.h" + +@implementation JSONPointer + ++ (id)valueForCollection:(id)collection withJSONPointer:(NSString *)pointer +{ + if (!pointer || ![pointer isKindOfClass:[NSString class]]) + { + return nil; + } + + // JSON Pointer RFC 6901 (April 2013) + + // Section 6: URI fragment evaluation: if a fragment, remove the fragment marker and de-escape. + pointer = [self trimAndUnescapeJSONPointerFragment:pointer]; + + // Section 5. Blank Pointer evaluates to complete JSON document. + if ([pointer isEqualToString:@""]) + { + return collection; + } + + // Section 3. Token without leading '/' is illegal, terminate evaluation + if (![pointer hasPrefix:@"/"]) + { + return nil; + } + + // Section 3. Legal leading '/', strip and continue; + pointer = [pointer substringFromIndex:1]; + + + // Section 3. Check for valid character ranges upper and lower limits. + if ([self pointerHasInvalidCharacters:pointer]) + { + return nil; + } + + // Section 4. Evaluate the tokens one by one starting with the root. + id object = collection; + NSArray *pointerComponents = [pointer componentsSeparatedByString:@"/"]; + for (NSString *component in pointerComponents) + { + object = [self valueForCollection:object withJSONPointerComponent:component]; + if (!object || [object isEqual:[NSNull null]]) + { + return nil; + } + } + return object; +} + ++ (NSString *)trimAndUnescapeJSONPointerFragment:(NSString *)pointer +{ + if ([pointer hasPrefix:@"#"]) + { + pointer = [pointer substringFromIndex:1]; + pointer = [pointer stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + } + return pointer; +} + ++ (BOOL)pointerHasInvalidCharacters:(NSString *)pointer +{ + static NSCharacterSet *illegalChars; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + illegalChars = [[NSCharacterSet characterSetWithRange:NSMakeRange(0x0000, 0x10FFFF)] invertedSet]; + }); + + return ([pointer rangeOfCharacterFromSet:illegalChars].location != NSNotFound); +} + ++ (id)valueForCollection:(id)collection withJSONPointerComponent:(NSString *)component +{ + if (collection == nil || collection == [NSNull null]) + { + // If the object is nil or null, terminate evaluation. + return nil; + } + + if ([collection isKindOfClass:[NSDictionary class]]) + { + return [(NSDictionary *)collection valueForJSONPointerComponent:component]; + } + + if ([collection isKindOfClass:[NSArray class]]) + { + return [(NSArray *)collection valueForJSONPointerComponent:component]; + } + + // Unspecified object type, terminate evaluation. + return nil; +} + +@end diff --git a/JSONTools/JSONTools-Prefix.pch b/JSONTools/JSONTools-Prefix.pch new file mode 100644 index 0000000..993a499 --- /dev/null +++ b/JSONTools/JSONTools-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ +@import Foundation; +#endif diff --git a/JSONTools/JSONTools.h b/JSONTools/JSONTools.h new file mode 100644 index 0000000..7114dde --- /dev/null +++ b/JSONTools/JSONTools.h @@ -0,0 +1,12 @@ +// +// JSONTools.h +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import Foundation; + +#import "JSONPointer.h" +#import "JSONPatch.h" +// #import "JSONSchema.h" -- Not ready yet diff --git a/JSONTools/NSArray+JSONPointer.h b/JSONTools/NSArray+JSONPointer.h new file mode 100644 index 0000000..1077bb6 --- /dev/null +++ b/JSONTools/NSArray+JSONPointer.h @@ -0,0 +1,49 @@ +// +// NSArray+JSONPointer.h +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import Foundation; + +@interface NSArray (JSONPointer) + +/** + * Returns the receiver's value corresponding to the supplied JSON Pointer reference. + * This is a convenience category for arrays to simplify this call: + * `[JSONPointer valueForCollection:self withJSONPointer:pointer]` + * + * @see JSON Pointer RFC 6901 (April 2013) + * + * @param pointer A string in the form of a JSON Pointer, like "/foo/bar/0" or "#/foo" + * + * @return The pointer's corresponding content value (or nil) in the collection. + */ +- (id)valueForJSONPointer:(NSString *)pointer; + +/** + * Given a single JSON Pointer component, like "12" from "/foo/bar/12", return the + * the receiver's corresponding value. In general you should use the JSONPointer + * class methods or valueForJSONPointer: above, as this method limits the scope. + * + * @param component A string in the form of a JSON Pointer component, like "12". + * + * @return The pointer component's corresponding content value (or nil). + */ +- (id)valueForJSONPointerComponent:(NSString *)component; + +/** + * Given a single JSON Pointer component, like "bar" from "/foo/bar/0", return the + * the receiver's corresponding array index. In general you should use the JSONPointer + * class methods instead, as this method is limited in the pointer's scope. + * + * @param component A string in the form of a JSON Pointer component, like "bar". + * + * @return The pointer component's corresponding array index, or NSNotFound. + */ +- (NSInteger)indexForJSONPointerComponent:(NSString *)component; + +@end diff --git a/JSONTools/NSArray+JSONPointer.m b/JSONTools/NSArray+JSONPointer.m new file mode 100644 index 0000000..0f4d810 --- /dev/null +++ b/JSONTools/NSArray+JSONPointer.m @@ -0,0 +1,79 @@ +// +// NSArray+JSONPointer.m +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "NSArray+JSONPointer.h" +#import "JSONPointer.h" + +@implementation NSArray (JSONPointer) + +- (id)valueForJSONPointer:(NSString *)pointer +{ + return [JSONPointer valueForCollection:self withJSONPointer:pointer]; +} + +- (id)valueForJSONPointerComponent:(NSString *)component +{ + NSInteger index = [self indexForJSONPointerComponent:component]; + + if (index == NSNotFound) + { + return nil; + } + + // Section 4. Valid array reference so navigate to object. + return self[index]; +} + +- (NSInteger)indexForJSONPointerComponent:(NSString *)component +{ + if (!component || ![component isKindOfClass:[NSString class]]) + return NSNotFound; + + //Section 4. Transform any escaped characters, in the order ~1 then ~0. + component = [component stringByReplacingOccurrencesOfString:@"~1" withString:@"/"]; + component = [component stringByReplacingOccurrencesOfString:@"~0" withString:@"~"]; + + // Section 4. Process array objects with ABNF Rule: 0x30/(0x31-39 *(0x30-0x39)) + + static NSCharacterSet *numberSet; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + numberSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"]; + }); + + if (![[component stringByTrimmingCharactersInSet:numberSet] isEqualToString:@""]) + { + return NSNotFound; + } + + // Section 4. Check for leading zero's + if ([component hasPrefix:@"0"] && + [component length] > 1) + { + return NSNotFound; + } + + // Section 4. Non-existant array element, terminate evaluation. + if ([component isEqualToString:@"-"]) + { + return NSNotFound; + } + + // Avoid any out-of-bounds exceptions + NSInteger index = [component integerValue]; + if (self.count <= index) + { + return NSNotFound; + } + + // Section 4. Valid array reference so navigate to object. + return index; +} + +@end diff --git a/JSONTools/NSDictionary+JSONPointer.h b/JSONTools/NSDictionary+JSONPointer.h new file mode 100644 index 0000000..4488fba --- /dev/null +++ b/JSONTools/NSDictionary+JSONPointer.h @@ -0,0 +1,48 @@ +// +// NSDictionary+JSONPointer.h +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import Foundation; + +@interface NSDictionary (JSONPointer) + +/** + * Returns the receiver's value corresponding to the supplied JSON Pointer reference. + * This is a convenience category for dictionaries to simplify this call: + * `[JSONPointer valueForCollection:self withJSONPointer:pointer]` + * + * @see JSON Pointer RFC 6901 (April 2013) + * + * @param pointer A string in the form of a JSON Pointer, like "/foo/bar/0" or "#/foo" + * + * @return The pointer's corresponding content value (or nil) in the collection. + */ +- (id)valueForJSONPointer:(NSString *)pointer; + +/** + * Given a single JSON Pointer component, like "bar" from "/foo/bar/0", return the + * the receiver's corresponding value. In general you should use the JSONPointer + * class methods or valueForJSONPointer: above, as this method limits the scope. + * + * @param component A string in the form of a JSON Pointer component, like "bar". + * + * @return The pointer component's corresponding content value (or nil). + */ +- (id)valueForJSONPointerComponent:(NSString *)component; + +/** + * Given a single JSON Pointer component, like "a~1b", return the + * component key after converting escape characters, like "a/b". + * + * @param component A string in the form of a JSON Pointer component. + * + * @return The component key after converting escape characters. + */ +- (NSString *)keyForJSONPointerComponent:(NSString *)component; + +@end diff --git a/JSONTools/NSDictionary+JSONPointer.m b/JSONTools/NSDictionary+JSONPointer.m new file mode 100644 index 0000000..2ed9405 --- /dev/null +++ b/JSONTools/NSDictionary+JSONPointer.m @@ -0,0 +1,41 @@ +// +// NSDictionary+JSONPointer.m +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +#import "NSDictionary+JSONPointer.h" +#import "JSONPointer.h" + +@implementation NSDictionary (JSONPointer) + +- (id)valueForJSONPointer:(NSString *)pointer +{ + return [JSONPointer valueForCollection:self withJSONPointer:pointer]; +} + +- (id)valueForJSONPointerComponent:(NSString *)component +{ + component = [self keyForJSONPointerComponent:component]; + if (!component) + return nil; + + // Section 4. If value is an object return the referenced property. + return self[component]; +} + +- (NSString *)keyForJSONPointerComponent:(NSString *)component +{ + if (!component || ![component isKindOfClass:[NSString class]]) + return nil; + + //Section 4. Transform any escaped characters, in the order ~1 then ~0. + component = [component stringByReplacingOccurrencesOfString:@"~1" withString:@"/"]; + component = [component stringByReplacingOccurrencesOfString:@"~0" withString:@"~"]; + return component; +} + +@end diff --git a/JSONToolsTests/JSONPatchApplyTests.m b/JSONToolsTests/JSONPatchApplyTests.m new file mode 100644 index 0000000..470094c --- /dev/null +++ b/JSONToolsTests/JSONPatchApplyTests.m @@ -0,0 +1,173 @@ +// +// JSONPatchApplyTests.m +// inspired by https://github.com/Starcounter-Jack/JSON-Patch +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import XCTest; +#import "JSONPatch.h" + +@interface JSONPatchApplyTests : XCTestCase + +@end + +@implementation JSONPatchApplyTests + +- (void)setUp +{ + [super setUp]; +} + +- (void)tearDown +{ + [super tearDown]; +} + +- (void)testShouldApplyAdd +{ + NSMutableDictionary *initial = [@{@"foo": @1, + @"baz": [@[@{@"qux": @"hello"}] mutableCopy]} mutableCopy]; + NSMutableDictionary *obj = [initial mutableCopy]; + NSMutableDictionary *expected = [initial mutableCopy]; + + + expected[@"bar"] = @[@1,@2,@3,@4]; + [JSONPatch applyPatches:@[@{@"op": @"add", + @"path": @"/bar", + @"value": @[@1, @2, @3, @4]}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply add patch, expected %@, found %@", expected, obj); + + + + expected[@"baz"][0] = @{@"qux": @"hello", + @"foo": @"world"}; + [JSONPatch applyPatches:@[@{@"opp": @"add", + @"path": @"/baz/0/foo", + @"value": @"world"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply add patch, expected %@, found %@", expected, obj); + + + + obj = [initial mutableCopy]; + expected = [initial mutableCopy]; + expected[@"bar"] = @YES; + [JSONPatch applyPatches:@[@{@"op": @"add", + @"path": @"/bar", + @"value": @YES}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply add patch, expected %@, found %@", expected, obj); + + + expected[@"bar"] = @NO; + [JSONPatch applyPatches:@[@{@"op": @"add", + @"path": @"/bar", + @"value": @NO}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply add patch, expected %@, found %@", expected, obj); + + obj = [initial mutableCopy]; + expected = [initial mutableCopy]; + expected[@"bar"] = [NSNull null]; + [JSONPatch applyPatches:@[@{@"op": @"add", + @"path": @"/bar", + @"value": [NSNull null]}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply add patch, expected %@, found %@", expected, obj); +} + +- (void)testShouldApplyRemove +{ + NSMutableDictionary *obj = [@{@"foo": @1, + @"baz": @[[@{@"qux": @"hello"} mutableCopy]], + @"bar": @[@1,@2,@3,@4]} mutableCopy]; + + NSMutableDictionary *expected = [obj mutableCopy]; + [expected removeObjectForKey:@"bar"]; + [JSONPatch applyPatches:@[@{@"op": @"remove", + @"path": @"/bar"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply remove patch, expected %@, found %@", expected, obj); + + expected = [@{@"foo": @1, + @"baz": @[[@{} mutableCopy]]} mutableCopy]; + [JSONPatch applyPatches:@[@{@"op": @"remove", + @"path": @"/baz/0/qux"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply remove patch, expected %@, found %@", expected, obj); +} + +- (void)testShouldApplyReplace +{ + NSMutableDictionary *obj = [@{@"foo": @1, + @"baz": @[[@{@"qux": @"hello"} mutableCopy]]} mutableCopy]; + + NSMutableDictionary *expected = [obj mutableCopy]; + expected[@"foo"] = @[@1,@2,@3,@4]; + [JSONPatch applyPatches:@[@{@"op": @"replace", + @"path": @"/foo", + @"value": @[@1,@2,@3,@4]}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply replace patch, expected %@, found %@", expected, obj); + + expected[@"baz"][0][@"qux"] = @"world"; + [JSONPatch applyPatches:@[@{@"op": @"replace", + @"path": @"/baz/0/qux", + @"value": @"world"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply replace patch, expected %@, found %@", expected, obj); +} + +- (void)testShouldApplyTest +{ + NSDictionary *obj = @{@"foo": @{@"bar": @[@1,@2,@5,@4]}}; + NSDictionary *testObj = @{@"bar": @[@1,@2,@5,@4]}; + NSNumber *result = [JSONPatch applyPatches:@[@{@"op": @"test", + @"path": @"/foo", + @"value": testObj}] toCollection:obj]; + XCTAssertNotNil(result, @"Failed to apply test patch, expected a non-nil test result"); + XCTAssertTrue([result boolValue], @"Failed to apply test patch, expected TRUE, found %@", result); + + result = [JSONPatch applyPatches:@[@{@"op": @"test", + @"path": @"/foo", + @"value": @[@1,@2]}] toCollection:obj]; + XCTAssertNotNil(result, @"Failed to apply test patch, expected a non-nil test result"); + XCTAssertFalse([result boolValue], @"Failed to apply test patch, expected FALSE, found %@", result); +} + +- (void)testShouldApplyMove +{ + NSMutableDictionary *obj = [@{@"foo": @1, + @"baz": [@[[@{@"qux": @"hello"} mutableCopy]] mutableCopy]} mutableCopy]; + NSMutableDictionary *expected = [obj mutableCopy]; + + expected[@"bar"] = @1; + [expected removeObjectForKey:@"foo"]; + [JSONPatch applyPatches:@[@{@"op": @"move", + @"from": @"/foo", + @"path": @"/bar"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply move patch, expected %@, found %@", expected, obj); + + [expected[@"baz"][0] removeAllObjects]; + [expected[@"baz"] addObject:@"hello"]; + [JSONPatch applyPatches:@[@{@"op": @"move", + @"from": @"/baz/0/qux", + @"path": @"/baz/1"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply move patch, expected %@, found %@", expected, obj); +} + +- (void)testShouldApplyCopy +{ + NSMutableDictionary *obj = [@{@"foo": @1, + @"baz": [@[[@{@"qux": @"hello"} mutableCopy]] mutableCopy]} mutableCopy]; + NSMutableDictionary *expected = [obj mutableCopy]; + + expected[@"bar"] = @1; + [JSONPatch applyPatches:@[@{@"op": @"copy", + @"from": @"/foo", + @"path": @"/bar"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply copy patch, expected %@, found %@", expected, obj); + + [expected[@"baz"] addObject:@"hello"]; + [JSONPatch applyPatches:@[@{@"op": @"copy", + @"from": @"/baz/0/qux", + @"path": @"/baz/1"}] toCollection:obj]; + XCTAssertEqualObjects(obj, expected, @"Failed to apply copy patch, expected %@, found %@", expected, obj); +} + +@end diff --git a/JSONToolsTests/JSONPointerTests.m b/JSONToolsTests/JSONPointerTests.m new file mode 100644 index 0000000..4bee9d7 --- /dev/null +++ b/JSONToolsTests/JSONPointerTests.m @@ -0,0 +1,303 @@ +// +// JSONPointerTests +// based in part on CWJSONPointer by Jonathan Dring (MIT/Copyright (c) 2014) +// +// JSONTools +// +// Copyright (C) 2014 Gregory Combs [gcombs at gmail] +// See LICENSE.txt for details. + +@import XCTest; +#import "JSONPointer.h" + +@interface JSONPointerTests : XCTestCase +@property (nonatomic,strong) NSDictionary *jsonRFC6901; +@property (nonatomic,strong) NSDictionary *jsonAdditional; +@end + +@implementation JSONPointerTests + +- (void)setUp +{ + [super setUp]; + _jsonRFC6901 = @{ + @"foo" : @[@"bar", @"baz"], + @"" : @0, + @"a/b" : @1, + @"c%d" : @2, + @"e^f" : @3, + @"g|h" : @4, + @"i\\j" : @5, + @"k\"l" : @6, + @" " : @7, + @"m~n" : @8 + }; + + _jsonAdditional = @{ + @"foo": @{ + @"bar": @{ + @"true": @YES, + @"false": @NO, + @"number": @55, + @"negative": @(-55), + @"string": @"mystring", + @"null": [NSNull null], + @"array": @[@1,@2,@3], + @"object": @{ + @"a": @1, + @"b": @2, + @"c": @3 + } + } + } + }; +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +#pragma mark - RFC6901 Specs + +/* RFC6901 String Representations + "" // the whole document + "/foo" ["bar", "baz"] + "/foo/0" "bar" + "/" 0 + "/a~1b" 1 + "/c%d" 2 + "/e^f" 3 + "/g|h" 4 + "/i\\j" 5 + "/k\"l" 6 + "/ " 7 + "/m~0n" 8 + */ + +- (void)testRFC6901StringRepresentations +{ + NSDictionary *json = _jsonRFC6901; + NSDictionary *tests = @{@"": json, + @"/foo": @[@"bar", @"baz"], + @"/foo/0": @"bar", + @"/": @0, + @"/a~1b": @1, + @"/c%d": @2, + @"/e^f": @3, + @"/g|h": @4, + @"/i\\j": @5, + @"/k\"l": @6, + @"/ ": @7, + @"/m~0n": @8 + }; + + [tests enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) { + XCTAssertEqualObjects([json valueForJSONPointer:key], obj, @"RFC6901 Test '%@' Failed", key); + }]; +} + +/* RFC6901 URI Fragment Representations + "#" the whole document + "#/foo" ["bar", "baz"] + "#/foo/0" "bar" + "#/" 0 + "#/a~1b" 1 + "#/c%25d" 2 + "#/e%5Ef" 3 + "#/g%7Ch" 4 + "#/i%5Cj" 5 + "#/k%22l" 6 + '#/%20" 7 + "#/m~0n" 8 + */ + +- (void)testRFC6901URIRepresentations +{ + NSDictionary *json = _jsonRFC6901; + NSDictionary *tests = @{@"#": json, + @"#/foo": @[@"bar", @"baz"], + @"#/foo/0": @"bar", + @"#/": @0, + @"#/a~1b": @1, + @"#/c%25d": @2, + @"#/e%5Ef": @3, + @"#/g%7Ch": @4, + @"#/i%5Cj": @5, + @"#/k%22l": @6, + @"#/%20": @7, + @"#/m~0n": @8 + }; + + [tests enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) { + XCTAssertEqualObjects([json valueForJSONPointer:key], obj, @"URI Test '%@' Failed", key); + }]; +} + +- (void)testRFC6901InvalidReferences +{ + NSDictionary *json = _jsonRFC6901; + NSString *prefix = @"Expected nil for invalid"; + + XCTAssertNil([json valueForJSONPointer:@"/u110000"], @"%@ character", prefix); + XCTAssertNil([json valueForJSONPointer:@"/c%25d"], @"%@ escaped non-fragment pointer", prefix); + XCTAssertNil([json valueForJSONPointer:@"/foo/00"], @"%@ array reference with leading zero's", prefix); + XCTAssertNil([json valueForJSONPointer:@"/foo/a"], @"%@ array reference", prefix); +} + +#pragma mark - Boolean Values + +- (void)testSuccessForBoolean +{ + NSDictionary *json = _jsonAdditional; + NSString *pointer = @"/foo/bar/true"; + NSNumber *result = [json valueForJSONPointer:pointer]; + [self expectClass:[NSNumber class] forResult:result]; + XCTAssertEqual([result boolValue], YES, @"Expected true from pointer %@", pointer); + + pointer = @"/foo/bar/false"; + result = [json valueForJSONPointer:pointer]; + [self expectBooleanForResult:result]; + XCTAssertEqual([result boolValue], NO, @"Expected false from pointer %@", pointer); +} + +- (void)testFailureForBoolean +{ + NSDictionary *json = _jsonAdditional; + NSDictionary *tests = @{@"array": @"/foo/bar/array", + @"negative": @"/foo/bar/negative", + @"object": @"/foo/bar/object", + @"number": @"/foo/bar/number", + @"string": @"/foo/bar/string", + @"null": @"/foo/bar/null"}; + [tests enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSNumber *result = [json valueForJSONPointer:obj]; + XCTAssertFalse([self isBooleanResult:result], @"Expected a non-boolean (%@) result for %@", key, obj); + }]; +} + +#pragma mark - Number Values + +- (void)testSuccessForNumber +{ + NSDictionary *json = _jsonAdditional; + NSNumber *result = [json valueForJSONPointer:@"/foo/bar/number"]; + [self expectClass:[NSNumber class] forResult:result]; + XCTAssertEqual([result intValue], 55, @"Expected result to equal 55, found %@", result); + + result = [json valueForJSONPointer:@"/foo/bar/negative"]; + [self expectClass:[NSNumber class] forResult:result]; + XCTAssertEqual([result intValue], -55, @"Expected result to equal -55, found %@", result); +} + +- (void)testFailureForNumber +{ + NSDictionary *json = _jsonAdditional; + NSDictionary *tests = @{@"string": @"/foo/bar/string", + @"array": @"/foo/bar/array", + @"object": @"/foo/bar/object", + @"null": @"/foo/bar/null"}; + [tests enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSNumber *result = [json valueForJSONPointer:obj]; + XCTAssertFalse([result isKindOfClass:[NSNumber class]], @"Expected a non-number result, found %@", result); + }]; +} + +#pragma mark - String Values + +- (void)testSuccessForString +{ + NSDictionary *json = _jsonAdditional; + NSString *result = [json valueForJSONPointer:@"/foo/bar/string"]; + [self expectClass:[NSString class] forResult:result]; + XCTAssertEqualObjects(result, @"mystring", @"Expected a matching string result."); +} + +- (void)testFailureForString +{ + NSDictionary *json = _jsonAdditional; + NSDictionary *tests = @{@"array": @"/foo/bar/array", + @"bool": @"/foo/bar/true", + @"number": @"/foo/bar/number", + @"object": @"/foo/bar/object", + @"null": @"/foo/bar/null"}; + [tests enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSString *result = [json valueForJSONPointer:obj]; + XCTAssertFalse([result isKindOfClass:[NSString class]], @"Expected a non-string result, found %@", result); + }]; +} + +#pragma mark - Array Values + +- (void)testSuccessForArray +{ + NSDictionary *json = _jsonAdditional; + NSArray *array = @[@1, + @2, + @3]; + NSArray *result = [json valueForJSONPointer:@"/foo/bar/array"]; + [self expectClass:[NSArray class] forResult:result]; + XCTAssertEqualObjects(result, array, @"Expected a matching array result, found %@", result); +} + +- (void)testFailureForArray +{ + NSDictionary *json = _jsonAdditional; + NSDictionary *tests = @{@"string": @"/foo/bar/string", + @"bool": @"/foo/bar/true", + @"number": @"/foo/bar/number", + @"object": @"/foo/bar/object", + @"null": @"/foo/bar/null"}; + [tests enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSArray *result = [json valueForJSONPointer:obj]; + XCTAssertFalse([result isKindOfClass:[NSArray class]], @"Expected a non-array result, found %@", result); + }]; +} + +#pragma mark - Dictionary Values + +- (void)testSuccessForDictionary +{ + NSDictionary *json = _jsonAdditional; + NSDictionary *dictionary = @{@"a": @1, + @"b": @2, + @"c": @3}; + NSDictionary *result = [json valueForJSONPointer:@"/foo/bar/object"]; + [self expectClass:[NSDictionary class] forResult:result]; + XCTAssertEqualObjects(result, dictionary, @"Expected a matching dictionary result, found %@", result); +} + +- (void)testFailureForDictionary +{ + NSDictionary *json = _jsonAdditional; + NSDictionary *tests = @{@"array": @"/foo/bar/array", + @"bool": @"/foo/bar/true", + @"number": @"/foo/bar/number", + @"string": @"/foo/bar/string", + @"null": @"/foo/bar/null"}; + [tests enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSDictionary *result = [json valueForJSONPointer:obj]; + XCTAssertFalse([result isKindOfClass:[NSDictionary class]], @"Expected a non-dictionary result, found %@", result); + }]; +} + +#pragma mark - Utilities + +- (void)expectClass:(Class)class forResult:(id)result +{ + XCTAssertTrue([result isKindOfClass:class], @"Expected an %@ result, found %@", class, [result class]); +} + +- (void)expectBooleanForResult:(id)result +{ + XCTAssertTrue([self isBooleanResult:result], @"Expected a boolean result, found %@", result); +} + +- (BOOL)isBooleanResult:(NSNumber *)result +{ + return ([result isKindOfClass:[NSNumber class]] && + (result.intValue == 0 || result.intValue == 1)); +} + +@end diff --git a/JSONToolsTests/JSONToolsTests-Info.plist b/JSONToolsTests/JSONToolsTests-Info.plist new file mode 100644 index 0000000..fefa1c5 --- /dev/null +++ b/JSONToolsTests/JSONToolsTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.sleestacks.JSONTools.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/JSONToolsTests/en.lproj/InfoPlist.strings b/JSONToolsTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/JSONToolsTests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8c5bfa0 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) +JSONTools +Copyright (C) 2014 Gregory Combs [gcombs at gmail] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index 04bb8b3..ebe19d1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,99 @@ -JSONTools +JSON Tools (Objective-C) +by [Gregory Combs](https://github.com/grgcombs) +(MIT License - 2014) ========= JSON Patch, JSON Pointer, and JSON Schema Validation in Objective-C + +This Objective-C library is a collection of classes and categories that implement three powerful new features (JSON Patch, JSON Pointer, JSON Schema) that work with JSON data (represented by NSDictionaries and NSArrays in Objective-C). Unit tests are included for each component. + +- [JSON Patch](https://tools.ietf.org/html/rfc6902) - IETF RFC6902: Create and apply operation patches (add, remove, copy, move, test, _get) to serially transform JSON Data. ***This functionality was inspired by [Starcounter-Jack's](https://github.com/Starcounter-Jack) [JavaScript implementation of JSON Patch](https://github.com/Starcounter-Jack/JSON-Patch).*** + - Example Patch Copy: + + ```objc + + #import "JSONTools.h" + + - (void)examplePatchCopy + { + /* assuming _obj is a (deeply mutable) NSMutableDictionary like this: + {"foo": 1, + "baz": [{"qux": "hello"}]} + */ + + NSDictionary *patch = @{@"op": @"copy", + @"from": @"/foo", + @"path": @"/bar"} + NSNumber *success = [JSONPatch applyPatches:@[patch] toCollection:_obj]; + + /* _obj will now look like this: + {"foo": 1, + "baz": [{"qux": "hello"}], + "bar": 1} + */ + } + + ``` + + - Example Patch Generation (JSON Diff): + + ```objc + + // This capability is still in progress + + ``` + + +- [JSON Pointer](https://tools.ietf.org/html/rfc6901) - IETF RFC6901: Reference and access values and objects within a hierarchical JSON structure using a concise path pattern notation. ***This functionality is based on [Jonathan Dring's](https://github.com/C-Works) [NSDictionary-CWJSONPointer](https://github.com/C-Works/NSDictionary-CWJSONPointer).*** + - Example: + + ```objc + + #import "JSONTools.h" + + - (void)exampleJSONPointer + { + /* assuming _obj is an NSDictionary like this: + { + "data": { + "foo": ["bar", "baz"], + "bork": { + "crud": "stuff", + "guts": "and things" } + } + } + */ + + NSString *result1 = [_obj valueForJSONPointer: @"/data/foo/1" ]; + // Yields -> "baz" + + NSString *result2 = [_obj valueForJSONPointer: @"/data/bork/guts"]; + // Yields -> "and things" + + NSDictionary *result3 = [_obj valueForJSONPointer: @"/data/bork"]; + // Yields -> {"crud": "stuff","guts": "and things"} } + + ``` + +- [JSON Schema](http://tools.ietf.org/html/draft-zyp-json-schema-04) with [Validation](http://tools.ietf.org/html/draft-fge-json-schema-validation-00) - IETF Draft v4, 2013: (*TBD / WIP*). ***This functionality will likely be based on [Sam Duke's](https://github.com/samskiter) [KiteJSONValidator](https://github.com/samskiter/KiteJSONValidator).*** + - Example: + + ```json + + { + "schema": { + "type": "array", + "items": { "$ref": "#/definitions/positiveInteger" }, + "definitions": { + "positiveInteger": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + } + } + }, + "valid_data": [0, 1, 2, 3, 4, 5], + "invalid_data": [-12, "Abysmal", null, -141] + } + ``` +