diff options
-rw-r--r-- | Foundation/GTMNSScanner+JSON.h | 38 | ||||
-rw-r--r-- | Foundation/GTMNSScanner+JSON.m | 83 | ||||
-rw-r--r-- | Foundation/GTMNSScanner+JSONTest.m | 125 | ||||
-rw-r--r-- | GTM.xcodeproj/project.pbxproj | 12 | ||||
-rw-r--r-- | GTMiPhone.xcodeproj/project.pbxproj | 10 | ||||
-rw-r--r-- | ReleaseNotes.txt | 4 |
6 files changed, 271 insertions, 1 deletions
diff --git a/Foundation/GTMNSScanner+JSON.h b/Foundation/GTMNSScanner+JSON.h new file mode 100644 index 0000000..22446c1 --- /dev/null +++ b/Foundation/GTMNSScanner+JSON.h @@ -0,0 +1,38 @@ +// +// GTMNSScanner+JSON.h +// +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Foundation/Foundation.h> + +// Utilities for NSScanner containing JSON +@interface NSScanner (GTMNSScannerJSONAdditions) + +// Grabs the first JSON Object (dictionary) that it finds and returns it +// in jsonString. We don't parse the json, we just return the first valid JSON +// dictionary we find. There are several other JSON parser packages that +// will actually parse the json for you. We recommend json-framework +// http://code.google.com/p/json-framework/ +- (BOOL)gtm_scanJSONObjectString:(NSString **)jsonString; + +// Grabs the first JSON Array (array) that it finds and returns it +// in jsonString. We don't parse the json, we just return the first valid JSON +// array we find. There are several other JSON parser packages that +// will actually parse the json for you. We recommend json-framework +// http://code.google.com/p/json-framework/ +- (BOOL)gtm_scanJSONArrayString:(NSString**)jsonString; + +@end diff --git a/Foundation/GTMNSScanner+JSON.m b/Foundation/GTMNSScanner+JSON.m new file mode 100644 index 0000000..1216698 --- /dev/null +++ b/Foundation/GTMNSScanner+JSON.m @@ -0,0 +1,83 @@ +// +// GTMNSScanner+JSON.m +// +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMDefines.h" +#import "GTMNSScanner+JSON.h" + +@implementation NSScanner (GTMNSScannerJSONAdditions) + +- (BOOL)gtm_scanJSONString:(NSString **)jsonString + startChar:(unichar)startChar + endChar:(unichar)endChar { + BOOL isGood = NO; + NSRange jsonRange = { NSNotFound, 0 }; + NSString *scanString = [self string]; + NSUInteger startLocation = [self scanLocation]; + NSUInteger length = [scanString length]; + NSUInteger blockOpen = 0; + NSCharacterSet *charsToSkip = [self charactersToBeSkipped]; + BOOL inQuoteMode = NO; + NSUInteger i; + for (i = startLocation; i < length; ++i) { + unichar jsonChar = [scanString characterAtIndex:i]; + if (jsonChar == startChar && !inQuoteMode) { + if (blockOpen == 0) { + jsonRange.location = i; + } + blockOpen += 1; + } else if (blockOpen == 0) { + // If we haven't opened our block skip over any characters in + // charsToSkip. + if (![charsToSkip characterIsMember:jsonChar]) { + break; + } + } else if (jsonChar == endChar && !inQuoteMode) { + blockOpen -= 1; + if (blockOpen == 0) { + i += 1; // Move onto next character + jsonRange.length = i - jsonRange.location; + break; + } + } else { + if (jsonChar == '"') { + inQuoteMode = !inQuoteMode; + } else if (inQuoteMode && jsonChar == '\\') { + // Skip the escaped character if it isn't the last one + if (i < length - 1) ++i; + } + } + } + [self setScanLocation:i]; + if (blockOpen == 0 && jsonRange.location != NSNotFound) { + isGood = YES; + if (jsonString) { + *jsonString = [scanString substringWithRange:jsonRange]; + } + } + return isGood; +} + +- (BOOL)gtm_scanJSONObjectString:(NSString **)jsonString { + return [self gtm_scanJSONString:jsonString startChar:'{' endChar:'}']; +} + +- (BOOL)gtm_scanJSONArrayString:(NSString**)jsonString { + return [self gtm_scanJSONString:jsonString startChar:'[' endChar:']']; +} + +@end diff --git a/Foundation/GTMNSScanner+JSONTest.m b/Foundation/GTMNSScanner+JSONTest.m new file mode 100644 index 0000000..08f5db1 --- /dev/null +++ b/Foundation/GTMNSScanner+JSONTest.m @@ -0,0 +1,125 @@ +// +// GTMNSScanner+JSONTest.m +// +// Copyright 2005-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMSenTestCase.h" +#import "GTMNSScanner+JSON.h" + +@interface GTMNSScanner_JSONTest : GTMTestCase +@end + +struct { + NSString *testString_; + NSString *resultString_; + BOOL isObject_; +} testStrings[] = { + { @"", nil, NO }, + { @"\"Empty String\"", nil, NO }, + { @"[\"Unclosed array\"", nil, NO }, + { @"[\"escape at end of unfinished string\\", nil, NO }, + { @"junk, [\"Unclosed array with junk before\"", nil, NO }, + { @"\"Unopened array\"]", nil, NO }, + { @"\"Unopened array with junk after\"] junk", nil, NO }, + { @"[\"array\"]", @"[\"array\"]", NO }, + { @"junk [\"array with junk\"]", @"[\"array with junk\"]", NO }, + { @"[\"array with junk\"], junk", @"[\"array with junk\"]", NO }, + { @"[[[\"nested array\"]]]", @"[[[\"nested array\"]]]", NO }, + { @"[[[\"badly nested array\"]]", nil, NO }, + { @"[[[\"over nested array\"]]]]", @"[[[\"over nested array\"]]]", NO }, + { @"[{]", @"[{]", NO }, + { @"[\"closer in quotes\":\"]\"]", @"[\"closer in quotes\":\"]\"]", NO }, + { @"[\"escaped closer\":\\]]", @"[\"escaped closer\":\\]", NO }, + { @"[\"double escape\":\\\\]", @"[\"double escape\":\\\\]", NO }, + { @"[\"doub esc quote\":\"\\\"]\"]", @"[\"doub esc quote\":\"\\\"]\"]", NO }, + { @"[\"opener in quotes\":\"[\"]", @"[\"opener in quotes\":\"[\"]", NO }, + { @"[\"escaped opener\":\\[]", nil, NO }, + { @"[\"escaped opener\":\\[]]", @"[\"escaped opener\":\\[]]", NO }, + { @"[\"doub esc quote\":\"\\\"[\"]", @"[\"doub esc quote\":\"\\\"[\"]", NO }, + { @"{\"Unclosed object\"", nil, YES }, + { @"junk, {\"Unclosed object with junk before\"", nil, YES }, + { @"\"Unopened object\"}", nil, YES }, + { @"\"Unopened object with junk after\"} junk", nil, YES }, + { @"{\"object\"}", @"{\"object\"}", YES }, + { @"junk, {\"object with junk\"}", @"{\"object with junk\"}", YES }, + { @"{\"object with junk\"}, junk", @"{\"object with junk\"}", YES }, + { @"{{{\"nested object\"}}}", @"{{{\"nested object\"}}}", YES }, + { @"{{{\"badly nested object\"}}", nil, YES }, + { @"{{{\"over nested object\"}}}}", @"{{{\"over nested object\"}}}", YES }, + { @"{[}", @"{[}", YES }, + { @"{\"closer in quotes\":\"}\"}", @"{\"closer in quotes\":\"}\"}", YES }, + { @"{\"escaped closer\":\\}}", @"{\"escaped closer\":\\}", YES }, + { @"{\"double escape\":\\\\}", @"{\"double escape\":\\\\}", YES }, + { @"{\"doub esc quote\":\"\\\"}\"}", @"{\"doub esc quote\":\"\\\"}\"}", YES }, + { @"{\"opener in quotes\":\"{\"}", @"{\"opener in quotes\":\"{\"}", YES }, + { @"{\"escaped opener\":\\{}", nil, YES }, + { @"{\"escaped opener\":\\{}}", @"{\"escaped opener\":\\{}}", YES }, + { @"{\"doub esc quote\":\"\\\"{\"}", @"{\"doub esc quote\":\"\\\"{\"}", YES }, +}; + +@implementation GTMNSScanner_JSONTest + +- (void)testJSONObject { + NSCharacterSet *set = [[NSCharacterSet illegalCharacterSet] invertedSet]; + for (size_t i = 0; i < sizeof(testStrings) / sizeof(testStrings[0]); ++i) { + NSScanner *scanner + = [NSScanner scannerWithString:testStrings[i].testString_]; + [scanner setCharactersToBeSkipped:set]; + NSString *array = nil; + BOOL goodArray = [scanner gtm_scanJSONArrayString:&array]; + scanner = [NSScanner scannerWithString:testStrings[i].testString_]; + [scanner setCharactersToBeSkipped:set]; + NSString *object = nil; + BOOL goodObject = [scanner gtm_scanJSONObjectString:&object]; + if (testStrings[i].resultString_) { + if (testStrings[i].isObject_) { + STAssertEqualStrings(testStrings[i].resultString_, + object, @"Test String: %@", testStrings[i]); + STAssertNil(array, @"Test String: %@", testStrings[i]); + STAssertTrue(goodObject, @"Test String: %@", testStrings[i]); + STAssertFalse(goodArray, @"Test String: %@", testStrings[i]); + } else { + STAssertEqualStrings(testStrings[i].resultString_, array, + @"Test String: %@", testStrings[i]); + STAssertNil(object, @"Test String: %@", testStrings[i]); + STAssertTrue(goodArray, @"Test String: %@", testStrings[i]); + STAssertFalse(goodObject, @"Test String: %@", testStrings[i]); + } + } else { + STAssertNil(object, @"Test String: %@", testStrings[i]); + STAssertNil(array, @"Test String: %@", testStrings[i]); + STAssertFalse(goodArray, @"Test String: %@", testStrings[i]); + STAssertFalse(goodObject, @"Test String: %@", testStrings[i]); + } + } +} + +- (void)testScanningCharacters { + NSCharacterSet *alphaSet = [NSCharacterSet alphanumericCharacterSet]; + NSString *testString = @"asdfasdf[]:,"; + NSScanner *scanner = [NSScanner scannerWithString:testString]; + [scanner setCharactersToBeSkipped:alphaSet]; + NSString *array = nil; + STAssertTrue([scanner gtm_scanJSONArrayString:&array], nil); + STAssertEqualStrings(array, @"[]", nil); + NSString *nextValue = nil; + STAssertTrue([scanner scanString:@":," intoString:&nextValue], nil); + STAssertEqualStrings(@":,", nextValue, nil); + scanner = [NSScanner scannerWithString:testString]; + STAssertFalse([scanner gtm_scanJSONArrayString:&array], nil); +} + +@end diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index d2a0e17..f83228a 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -172,6 +172,9 @@ 8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */; }; 8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */; }; 8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BD35B910FB22980009058F5 /* GTMNSScanner+JSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BD35B8E0FB22980009058F5 /* GTMNSScanner+JSON.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BD35B920FB22980009058F5 /* GTMNSScanner+JSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD35B8F0FB22980009058F5 /* GTMNSScanner+JSON.m */; }; + 8BD35B940FB22986009058F5 /* GTMNSScanner+JSONTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD35B900FB22980009058F5 /* GTMNSScanner+JSONTest.m */; }; 8BE281B00DEC7E930035B3F8 /* GTMNSAppleScript+Handler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3344190DBF7A36009FD32C /* GTMNSAppleScript+Handler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BE281B10DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Handler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B33441C0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BE281B20DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Foundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B33441F0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -482,6 +485,9 @@ 8BAA9E370F7C19D500DF4F12 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = UnitTesting/GTMUIUnitTestingHarness/English.lproj/MainMenu.xib; sourceTree = "<group>"; }; 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = "<absolute>"; }; 8BC04D140DB0061300C2D1CA /* RunMacOSUnitTests.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = RunMacOSUnitTests.sh; sourceTree = "<group>"; }; + 8BD35B8E0FB22980009058F5 /* GTMNSScanner+JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSScanner+JSON.h"; sourceTree = "<group>"; }; + 8BD35B8F0FB22980009058F5 /* GTMNSScanner+JSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSScanner+JSON.m"; sourceTree = "<group>"; }; + 8BD35B900FB22980009058F5 /* GTMNSScanner+JSONTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSScanner+JSONTest.m"; sourceTree = "<group>"; }; 8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFourCharCode.m; sourceTree = "<group>"; }; 8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFourCharCodeTest.m; sourceTree = "<group>"; }; 8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMFourCharCode.h; sourceTree = "<group>"; }; @@ -983,6 +989,9 @@ 8B6C15910F356E6400E51E5D /* GTMNSObject+KeyValueObserving.h */, 8B6C15920F356E6400E51E5D /* GTMNSObject+KeyValueObserving.m */, 8B6C161B0F3580DA00E51E5D /* GTMNSObject+KeyValueObservingTest.m */, + 8BD35B8E0FB22980009058F5 /* GTMNSScanner+JSON.h */, + 8BD35B8F0FB22980009058F5 /* GTMNSScanner+JSON.m */, + 8BD35B900FB22980009058F5 /* GTMNSScanner+JSONTest.m */, F42597760E23FE3A003BEA3E /* GTMNSString+FindFolder.h */, F42597770E23FE3A003BEA3E /* GTMNSString+FindFolder.m */, F42597780E23FE3A003BEA3E /* GTMNSString+FindFolderTest.m */, @@ -1178,6 +1187,7 @@ 10998EF50F4B5D1A007F179D /* GTMTransientRootPortProxy.h in Headers */, 8B40994B0F93C5CC00DF540E /* GTMUILocalizer.h in Headers */, 8BFE13B60FB0F2C0001BE894 /* GTMABAddressBook.h in Headers */, + 8BD35B910FB22980009058F5 /* GTMNSScanner+JSON.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1595,6 +1605,7 @@ F95B567F0F46208E0051A6F1 /* GTMSQLiteTest.m in Sources */, 10998F8B0F4B5F1B007F179D /* GTMTransientRootProxyTest.m in Sources */, 108930850F4CCB380018D4A0 /* GTMTransientRootPortProxyTest.m in Sources */, + 8BD35B940FB22986009058F5 /* GTMNSScanner+JSONTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1659,6 +1670,7 @@ 10998EF40F4B5D1A007F179D /* GTMTransientRootPortProxy.m in Sources */, 8B40994C0F93C5CC00DF540E /* GTMUILocalizer.m in Sources */, 8BFE13B70FB0F2C0001BE894 /* GTMABAddressBook.m in Sources */, + 8BD35B920FB22980009058F5 /* GTMNSScanner+JSON.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj index 8fc1472..24c7597 100644 --- a/GTMiPhone.xcodeproj/project.pbxproj +++ b/GTMiPhone.xcodeproj/project.pbxproj @@ -67,6 +67,8 @@ 8BC04A750DAF145200C2D1CA /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */; }; 8BC04D480DB0088500C2D1CA /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC04D470DB0088500C2D1CA /* libz.dylib */; }; 8BC04DE80DB023D400C2D1CA /* ReleaseNotes.txt in Resources */ = {isa = PBXBuildFile; fileRef = 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */; }; + 8BD35C920FB234E1009058F5 /* GTMNSScanner+JSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD35C900FB234E1009058F5 /* GTMNSScanner+JSON.m */; }; + 8BD35C930FB234E1009058F5 /* GTMNSScanner+JSONTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BD35C910FB234E1009058F5 /* GTMNSScanner+JSONTest.m */; }; 8BDA25130E759A6400C9769D /* GTMHTTPServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */; }; 8BDA25140E759A6500C9769D /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */; }; 8BE839890E89C74B00C611B0 /* GTMDebugThreadValidation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BE839870E89C74A00C611B0 /* GTMDebugThreadValidation.m */; }; @@ -188,6 +190,9 @@ 8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersion.m; sourceTree = "<group>"; }; 8BC04D470DB0088500C2D1CA /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ReleaseNotes.txt; sourceTree = "<group>"; }; + 8BD35C8F0FB234E1009058F5 /* GTMNSScanner+JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSScanner+JSON.h"; sourceTree = "<group>"; }; + 8BD35C900FB234E1009058F5 /* GTMNSScanner+JSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSScanner+JSON.m"; sourceTree = "<group>"; }; + 8BD35C910FB234E1009058F5 /* GTMNSScanner+JSONTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSScanner+JSONTest.m"; sourceTree = "<group>"; }; 8BE839870E89C74A00C611B0 /* GTMDebugThreadValidation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDebugThreadValidation.m; sourceTree = "<group>"; }; 8BE839880E89C74A00C611B0 /* GTMDebugThreadValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDebugThreadValidation.h; sourceTree = "<group>"; }; 8BE83A650E8B059A00C611B0 /* GTMDebugThreadValidationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDebugThreadValidationTest.m; sourceTree = "<group>"; }; @@ -345,6 +350,9 @@ 8B6C18710F3769D200E51E5D /* GTMNSObject+KeyValueObserving.h */, 8B6C18720F3769D200E51E5D /* GTMNSObject+KeyValueObserving.m */, 8B6C18730F3769D200E51E5D /* GTMNSObject+KeyValueObservingTest.m */, + 8BD35C8F0FB234E1009058F5 /* GTMNSScanner+JSON.h */, + 8BD35C900FB234E1009058F5 /* GTMNSScanner+JSON.m */, + 8BD35C910FB234E1009058F5 /* GTMNSScanner+JSONTest.m */, 8BC047870DAE928A00C2D1CA /* GTMNSString+HTML.h */, 8BC047880DAE928A00C2D1CA /* GTMNSString+HTML.m */, 8BC047890DAE928A00C2D1CA /* GTMNSString+HTMLTest.m */, @@ -626,6 +634,8 @@ 8B6C18750F3769D200E51E5D /* GTMNSObject+KeyValueObservingTest.m in Sources */, 8BFE15C60FB0F764001BE894 /* GTMABAddressBook.m in Sources */, 8BFE15C80FB0F764001BE894 /* GTMABAddressBookTest.m in Sources */, + 8BD35C920FB234E1009058F5 /* GTMNSScanner+JSON.m in Sources */, + 8BD35C930FB234E1009058F5 /* GTMNSScanner+JSONTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index 24b836f..643eea9 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -288,7 +288,9 @@ Changes since 1.5.1 - Moved GTMABAddressBook out of iPhone and into the AddressBook directory, because it now works on both the Desktop and the iPhone giving you a single interface to do AddressBook work on both platforms. - + +- Added GTMNSScanner+JSON for scanning out JSON objects and arrays. We don't + parse JSON as there are several other frameworks out there for doing that. Release 1.5.1 Changes since 1.5.0 |