Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit 88c9160a54142b3872d1508691078dcb3b5032f9 0 parents
@timburks authored
Showing with 2,589 additions and 0 deletions.
  1. +19 −0 CREDITS
  2. +41 −0 Nukefile
  3. +11 −0 README
  4. +76 −0 extra/bench.m
  5. +1 −0  extra/make
  6. +189 −0 objc/JSON.h
  7. +802 −0 objc/JSON.m
  8. +10 −0 test/README
  9. +14 −0 test/format/HumanReadable.json
  10. +1 −0  test/format/input.json
  11. +22 −0 test/json.org/1.json
  12. +44 −0 test/json.org/1.plist
  13. +11 −0 test/json.org/2.json
  14. +37 −0 test/json.org/2.plist
  15. +26 −0 test/json.org/3.json
  16. +54 −0 test/json.org/3.plist
  17. +88 −0 test/json.org/4.json
  18. +185 −0 test/json.org/4.plist
  19. +27 −0 test/json.org/5.json
  20. +1 −0  test/jsonchecker/fail1.json
  21. +1 −0  test/jsonchecker/fail10.json
  22. +1 −0  test/jsonchecker/fail11.json
  23. +1 −0  test/jsonchecker/fail12.json
  24. +1 −0  test/jsonchecker/fail13.json
  25. +1 −0  test/jsonchecker/fail14.json
  26. +1 −0  test/jsonchecker/fail15.json
  27. +1 −0  test/jsonchecker/fail16.json
  28. +1 −0  test/jsonchecker/fail17.json
  29. +1 −0  test/jsonchecker/fail18.json
  30. +1 −0  test/jsonchecker/fail19.json
  31. +1 −0  test/jsonchecker/fail2.json
  32. +1 −0  test/jsonchecker/fail20.json
  33. +1 −0  test/jsonchecker/fail21.json
  34. +1 −0  test/jsonchecker/fail22.json
  35. +1 −0  test/jsonchecker/fail23.json
  36. +1 −0  test/jsonchecker/fail24.json
  37. +1 −0  test/jsonchecker/fail25.json
  38. +1 −0  test/jsonchecker/fail26.json
  39. +2 −0  test/jsonchecker/fail27.json
  40. +2 −0  test/jsonchecker/fail28.json
  41. +1 −0  test/jsonchecker/fail29.json
  42. +1 −0  test/jsonchecker/fail3.json
  43. +1 −0  test/jsonchecker/fail30.json
  44. +1 −0  test/jsonchecker/fail31.json
  45. +1 −0  test/jsonchecker/fail32.json
  46. +1 −0  test/jsonchecker/fail33.json
  47. +1 −0  test/jsonchecker/fail4.json
  48. +1 −0  test/jsonchecker/fail5.json
  49. +1 −0  test/jsonchecker/fail6.json
  50. +1 −0  test/jsonchecker/fail7.json
  51. +1 −0  test/jsonchecker/fail8.json
  52. +1 −0  test/jsonchecker/fail9.json
  53. +58 −0 test/jsonchecker/pass1.json
  54. +1 −0  test/jsonchecker/pass2.json
  55. +6 −0 test/jsonchecker/pass3.json
  56. +16 −0 test/original/Errors.h
  57. +269 −0 test/original/Errors.m
  58. +12 −0 test/original/Pretty.h
  59. +42 −0 test/original/Pretty.m
  60. +12 −0 test/original/Types.h
  61. +146 −0 test/original/Types.m
  62. +13 −0 test/rfc4627/a.json
  63. +31 −0 test/rfc4627/a.plist
  64. +22 −0 test/rfc4627/b.json
  65. +42 −0 test/rfc4627/b.plist
  66. +61 −0 test/test_examples.nu
  67. +32 −0 test/types/array.plist
  68. +53 −0 test/types/number.plist
  69. +35 −0 test/types/object.plist
  70. +45 −0 test/types/string.plist
19 CREDITS
@@ -0,0 +1,19 @@
+Blake Seely
+ A lot of the inspiration to early versions of this framework
+ came from Blake's BSJSONAdditions project.
+
+Marc Lehmann
+ Part of the inspiration for my JSON work has been from Marc's
+ JSON::XS Perl module. I also adopted the way he organised the
+ tests. Having a separate fixture for testing error conditions
+ makes a lot of sense.
+
+Jens Alfke <jens@mooseyard.com> - http://mooseyard.com/Jens
+ Jens emailed me out of the blue one day with a couple of patches
+ that gave a speedup of 11x for generation and 5x for parsing of
+ the long (12k) JSON string I've been using for testing.
+
+Greg Bolsinga
+ Provided patches for dropping the dependency on AppKit and thus
+ truly making this a Foundation framework, and for building a
+ static library suitable for use with the iPhone.
41 Nukefile
@@ -0,0 +1,41 @@
+;;
+;; Nukefile for JSON
+;;
+;; Commands:
+;; nuke - builds TouchJSON as a framework
+;; nuke test - runs the unit tests in the NuTests directory
+;; nuke install - installs TouchJSON in /Library/Frameworks
+;; nuke clean - removes build artifacts
+;; nuke clobber - removes build artifacts and TouchJSON.framework
+;;
+;; The "nuke" build tool is installed with Nu (http://programming.nu)
+;;
+
+;; the @variables below are instance variables of a NukeProject.
+;; for details, see tools/nuke in the Nu source distribution.
+
+;; source files
+(set @m_files (filelist "^objc/.*.m$"))
+
+;; framework description
+(set @framework "JSON")
+(set @framework_identifier "nu.programming.json")
+(set @framework_creator_code "????")
+
+(set @cflags "-g -std=gnu99 -I Source")
+(set @ldflags "-framework Foundation")
+
+(compilation-tasks)
+(framework-tasks)
+
+(task "clobber" => "clean" is
+ (SH "rm -rf #{@framework_dir}")) ;; @framework_dir is defined by the nuke framework-tasks macro
+
+(task "default" => "framework")
+
+(task "install" => "framework" is
+ (SH "sudo rm -rf /Library/Frameworks/#{@framework}.framework")
+ (SH "ditto #{@framework}.framework /Library/Frameworks/#{@framework}.framework"))
+
+(task "test" => "framework" is
+ (SH "nutest test/test_*.nu"))
11 README
@@ -0,0 +1,11 @@
+
+This is a Nu-ish repackaging of Stig Brautaset's json-framework.
+
+--- Quoting Stig: ---
+JSON is a light-weight data interchange format. It's easy to read and
+write for humans and computers alike. This framework implements a strict
+JSON parser and generator in Objective-C.
+
+All resources related to this framework can be found at the project
+site: http://code.google.com/p/json-framework/
+---------------------
76 extra/bench.m
@@ -0,0 +1,76 @@
+/*
+Copyright (C) 25/04/2008 Stig Brautaset. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#import <stdio.h>
+#import "JSON.h"
+
+#define COUNT 500
+
+int main(int argc, char **argv) {
+ NSAutoreleasePool *outer = [NSAutoreleasePool new];
+
+ if (argc != 2) {
+ printf("Usage: %s file-containing-json\n", argv[0]);
+ return 1;
+ }
+
+ SBJSON *json = [SBJSON new];
+ NSString *filename = [NSString stringWithCString:argv[1]];
+ NSString *repr = [NSString stringWithContentsOfFile:filename];
+
+ NSAutoreleasePool *inner = [NSAutoreleasePool new];
+ NSDate *start = [NSDate date];
+ for (int i = 0; i < COUNT; i++) {
+ [json objectWithString:repr error:NULL];
+ [json objectWithString:repr error:NULL];
+ [json objectWithString:repr error:NULL];
+ [json objectWithString:repr error:NULL];
+ [json objectWithString:repr error:NULL];
+ }
+ double duration = -[start timeIntervalSinceNow];
+ printf("Decode: %f\n", 5 * COUNT / duration);
+ [inner release];
+
+ id object = [repr JSONValue];
+ inner = [NSAutoreleasePool new];
+ start = [NSDate date];
+ for (int i = 0; i < COUNT; i++) {
+ [json stringWithObject:object error:NULL];
+ [json stringWithObject:object error:NULL];
+ [json stringWithObject:object error:NULL];
+ [json stringWithObject:object error:NULL];
+ [json stringWithObject:object error:NULL];
+ }
+ duration = -[start timeIntervalSinceNow];
+ printf("Encode: %f\n", 5 * COUNT / duration);
+ [inner release];
+
+ [outer release];
+ return 0;
+}
1  extra/make
@@ -0,0 +1 @@
+gcc bench.m -I objc -framework JSON -std=gnu99 -framework Foundation
189 objc/JSON.h
@@ -0,0 +1,189 @@
+/*
+Copyright (c) 2007, Stig Brautaset. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used
+to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+@mainpage A strict JSON parser and generator for Objective-C
+
+JSON (JavaScript Object Notation) is a lightweight data-interchange
+format. This framework provides two apis for parsing and generating
+JSON. One standard object-based and a higher level api consisting of
+categories added to existing Objective-C classes.
+
+Learn more on the http://code.google.com/p/json-framework project site.
+*/
+
+#import <Foundation/Foundation.h>
+
+extern NSString * SBJSONErrorDomain;
+
+enum
+{
+ EUNSUPPORTED = 1,
+ EPARSENUM,
+ EPARSE,
+ EFRAGMENT,
+ ECTRL,
+ EUNICODE,
+ EDEPTH,
+ EESCAPE,
+ ETRAILCOMMA,
+ ETRAILGARBAGE,
+ EEOF,
+ EINPUT
+};
+
+/**
+@brief A strict JSON parser and generator
+
+This is the parser and generator underlying the categories added to
+NSString and various other objects.
+
+Objective-C types are mapped to JSON types and back in the following way:
+
+@li NSNull -> Null -> NSNull
+@li NSString -> String -> NSMutableString
+@li NSArray -> Array -> NSMutableArray
+@li NSDictionary -> Object -> NSMutableDictionary
+@li NSNumber (-initWithBool:) -> Boolean -> NSNumber -initWithBool:
+@li NSNumber -> Number -> NSDecimalNumber
+
+In JSON the keys of an object must be strings. NSDictionary keys need
+not be, but attempting to convert an NSDictionary with non-string keys
+into JSON will throw an exception.
+
+NSNumber instances created with the +numberWithBool: method are
+converted into the JSON boolean "true" and "false" values, and vice
+versa. Any other NSNumber instances are converted to a JSON number the
+way you would expect. JSON numbers turn into NSDecimalNumber instances,
+as we can thus avoid any loss of precision.
+
+Strictly speaking correctly formed JSON text must have <strong>exactly
+one top-level container</strong>. (Either an Array or an Object.) Scalars,
+i.e. nulls, numbers, booleans and strings, are not valid JSON on their own.
+It can be quite convenient to pretend that such fragments are valid
+JSON however, and this class lets you do so.
+
+This class does its best to be as strict as possible, both in what it
+accepts and what it generates. (Other than the above mentioned support
+for JSON fragments.) For example, it does not support trailing commas
+in arrays or objects. Nor does it support embedded comments, or
+anything else not in the JSON specification.
+
+*/
+@interface SBJSON : NSObject
+{
+ BOOL humanReadable;
+ BOOL sortKeys;
+ NSUInteger maxDepth;
+
+ @private
+ // Used temporarily during scanning/generation
+ NSUInteger depth;
+ const char *c;
+}
+
+/// Whether we are generating human-readable (multiline) JSON
+/**
+ Set whether or not to generate human-readable JSON. The default is NO, which produces
+ JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable
+ JSON with linebreaks after each array value and dictionary key/value pair, indented two
+ spaces per nesting level.
+ */
+@property BOOL humanReadable;
+
+/// Whether or not to sort the dictionary keys in the output
+/** The default is to not sort the keys. */
+@property BOOL sortKeys;
+
+/// The maximum depth the parser will go to
+/** Defaults to 512. */
+@property NSUInteger maxDepth;
+
+/// Return JSON representation of an array or dictionary
+- (NSString*)stringWithObject:(id)value error:(NSError**)error;
+
+/// Return JSON representation of any legal JSON value
+- (NSString*)stringWithFragment:(id)value error:(NSError**)error;
+
+/// Return the object represented by the given string
+- (id)objectWithString:(NSString*)jsonrep error:(NSError**)error;
+
+/// Return the fragment represented by the given string
+- (id)fragmentWithString:(NSString*)jsonrep error:(NSError**)error;
+
+/// Return JSON representation (or fragment) for the given object
+- (NSString*)stringWithObject:(id)value
+allowScalar:(BOOL)x
+error:(NSError**)error;
+
+/// Parse the string and return the represented object (or scalar)
+- (id)objectWithString:(id)value
+allowScalar:(BOOL)x
+error:(NSError**)error;
+
+@end
+
+/// Adds JSON generation to NSObject subclasses
+@interface NSObject (NSObject_SBJSON)
+
+/**
+ @brief Returns a string containing the receiver encoded as a JSON fragment.
+
+ This method is added as a category on NSObject but is only actually
+ supported for the following objects:
+ @li NSDictionary
+ @li NSArray
+ @li NSString
+ @li NSNumber (also used for booleans)
+ @li NSNull
+ */
+- (NSString *)JSONFragment;
+
+/**
+ @brief Returns a string containing the receiver encoded in JSON.
+
+ This method is added as a category on NSObject but is only actually
+ supported for the following objects:
+ @li NSDictionary
+ @li NSArray
+ */
+- (NSString *)JSONRepresentation;
+
+@end
+
+/// Adds JSON parsing to NSString
+@interface NSString (NSString_SBJSON)
+
+/// Returns the object represented in the receiver, or nil on error.
+- (id)JSONFragmentValue;
+
+/// Returns the dictionary or array represented in the receiver, or nil on error.
+- (id)JSONValue;
+
+@end
802 objc/JSON.m
@@ -0,0 +1,802 @@
+/*
+Copyright (C) 2008 Stig Brautaset. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used
+to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#import "JSON.h"
+
+NSString *SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain";
+
+@interface SBJSON (Generator)
+- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json error:(NSError**)error;
+- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json error:(NSError**)error;
+- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json error:(NSError**)error;
+- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json error:(NSError**)error;
+- (NSString*)indent;
+@end
+
+@interface SBJSON (Scanner)
+- (BOOL)scanValue:(NSObject **)o error:(NSError **)error;
+- (BOOL)scanRestOfArray:(NSMutableArray **)o error:(NSError **)error;
+- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o error:(NSError **)error;
+- (BOOL)scanRestOfNull:(NSNull **)o error:(NSError **)error;
+- (BOOL)scanRestOfFalse:(NSNumber **)o error:(NSError **)error;
+- (BOOL)scanRestOfTrue:(NSNumber **)o error:(NSError **)error;
+- (BOOL)scanRestOfString:(NSMutableString **)o error:(NSError **)error;
+// Cannot manage without looking at the first digit
+- (BOOL)scanNumber:(NSNumber **)o error:(NSError **)error;
+- (BOOL)scanHexQuad:(unichar *)x error:(NSError **)error;
+- (BOOL)scanUnicodeChar:(unichar *)x error:(NSError **)error;
+- (BOOL)scanIsAtEnd;
+@end
+
+#define skipWhitespace(c) while (isspace(*c)) c++
+#define skipDigits(c) while (isdigit(*c)) c++
+
+static NSError *err(int code, NSString *str)
+{
+ NSDictionary *ui = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey];
+ return [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:ui];
+}
+
+static NSError *errWithUnderlier(int code, NSError **u, NSString *str)
+{
+ if (!u)
+ return err(code, str);
+
+ NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:
+ str, NSLocalizedDescriptionKey,
+ *u, NSUnderlyingErrorKey,
+ nil];
+ return [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:ui];
+}
+
+@implementation SBJSON
+
+static char ctrl[0x22];
+
++ (void)initialize
+{
+ ctrl[0] = '\"';
+ ctrl[1] = '\\';
+ for (int i = 1; i < 0x20; i++)
+ ctrl[i+1] = i;
+ ctrl[0x21] = 0;
+}
+
+- (id)init
+{
+ if (self = [super init]) {
+ [self setMaxDepth:512];
+ }
+ return self;
+}
+
+/**
+ Returns a string containing JSON representation of the passed in value, or nil on error.
+ If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error.
+
+ @param value any instance that can be represented as a JSON fragment
+ @param allowScalar wether to return json fragments for scalar objects
+ @param error used to return an error by reference (pass NULL if this is not desired)
+ */
+- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error
+{
+ depth = 0;
+ NSMutableString *json = [NSMutableString stringWithCapacity:128];
+
+ NSError *err2 = nil;
+ if (!allowScalar && ![value isKindOfClass:[NSDictionary class]] && ![value isKindOfClass:[NSArray class]]) {
+ err2 = err(EFRAGMENT, @"Not valid type for JSON");
+
+ }
+ else if ([self appendValue:value into:json error:&err2]) {
+ return json;
+ }
+
+ if (error)
+ *error = err2;
+ return nil;
+}
+
+/**
+ Returns a string containing JSON representation of the passed in value, or nil on error.
+ If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error.
+
+ @param value any instance that can be represented as a JSON fragment
+ @param error used to return an error by reference (pass NULL if this is not desired)
+ */
+- (NSString*)stringWithFragment:(id)value error:(NSError**)error
+{
+ return [self stringWithObject:value allowScalar:YES error:error];
+}
+
+/**
+ Returns a string containing JSON representation of the passed in value, or nil on error.
+ If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error.
+
+ @param value a NSDictionary or NSArray instance
+ @param error used to return an error by reference (pass NULL if this is not desired)
+ */
+- (NSString*)stringWithObject:(id)value error:(NSError**)error
+{
+ return [self stringWithObject:value allowScalar:NO error:error];
+}
+
+- (NSString*)indent
+{
+ return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0];
+}
+
+- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json error:(NSError**)error
+{
+ if ([fragment isKindOfClass:[NSDictionary class]]) {
+ if (![self appendDictionary:fragment into:json error:error])
+ return NO;
+
+ }
+ else if ([fragment isKindOfClass:[NSArray class]]) {
+ if (![self appendArray:fragment into:json error:error])
+ return NO;
+
+ }
+ else if ([fragment isKindOfClass:[NSString class]]) {
+ if (![self appendString:fragment into:json error:error])
+ return NO;
+
+ }
+ else if ([fragment isKindOfClass:[NSNumber class]]) {
+ if ('c' == *[fragment objCType])
+ [json appendString:[fragment boolValue] ? @"true" : @"false"];
+ else
+ [json appendString:[fragment stringValue]];
+
+ }
+ else if ([fragment isKindOfClass:[NSNull class]]) {
+ [json appendString:@"null"];
+
+ }
+ else {
+ *error = err(EUNSUPPORTED, [NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]);
+ return NO;
+ }
+ return YES;
+}
+
+- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json error:(NSError**)error
+{
+ [json appendString:@"["];
+ depth++;
+
+ BOOL addComma = NO;
+ for (id value in fragment) {
+ if (addComma)
+ [json appendString:@","];
+ else
+ addComma = YES;
+
+ if ([self humanReadable])
+ [json appendString:[self indent]];
+
+ if (![self appendValue:value into:json error:error]) {
+ return NO;
+ }
+ }
+
+ depth--;
+ if ([self humanReadable] && [fragment count])
+ [json appendString:[self indent]];
+ [json appendString:@"]"];
+ return YES;
+}
+
+- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json error:(NSError**)error
+{
+ [json appendString:@"{"];
+ depth++;
+
+ NSString *colon = [self humanReadable] ? @" : " : @":";
+ BOOL addComma = NO;
+ NSArray *keys = [fragment allKeys];
+ if (self.sortKeys)
+ keys = [keys sortedArrayUsingSelector:@selector(compare:)];
+
+ for (id value in keys) {
+ if (addComma)
+ [json appendString:@","];
+ else
+ addComma = YES;
+
+ if ([self humanReadable])
+ [json appendString:[self indent]];
+
+ if (![value isKindOfClass:[NSString class]]) {
+ *error = err(EUNSUPPORTED, @"JSON object key must be string");
+ return NO;
+ }
+
+ if (![self appendString:value into:json error:error])
+ return NO;
+
+ [json appendString:colon];
+ if (![self appendValue:[fragment objectForKey:value] into:json error:error]) {
+ *error = err(EUNSUPPORTED, [NSString stringWithFormat:@"Unsupported value for key %@ in object", value]);
+ return NO;
+ }
+ }
+
+ depth--;
+ if ([self humanReadable] && [fragment count])
+ [json appendString:[self indent]];
+ [json appendString:@"}"];
+ return YES;
+}
+
+- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json error:(NSError**)error
+{
+
+ static NSMutableCharacterSet *kEscapeChars;
+ if( ! kEscapeChars ) {
+ kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain];
+ [kEscapeChars addCharactersInString: @"\"\\"];
+ }
+
+ [json appendString:@"\""];
+
+ NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars];
+ if ( !esc.length ) {
+ // No special chars -- can just add the raw string:
+ [json appendString:fragment];
+
+ }
+ else {
+ NSUInteger length = [fragment length];
+ for (NSUInteger i = 0; i < length; i++) {
+ unichar uc = [fragment characterAtIndex:i];
+ switch (uc) {
+ case '"': [json appendString:@"\\\""]; break;
+ case '\\': [json appendString:@"\\\\"]; break;
+ case '\t': [json appendString:@"\\t"]; break;
+ case '\n': [json appendString:@"\\n"]; break;
+ case '\r': [json appendString:@"\\r"]; break;
+ case '\b': [json appendString:@"\\b"]; break;
+ case '\f': [json appendString:@"\\f"]; break;
+ default:
+ if (uc < 0x20) {
+ [json appendFormat:@"\\u%04x", uc];
+ }
+ else {
+ [json appendFormat:@"%C", uc];
+ }
+ break;
+
+ }
+ }
+ }
+
+ [json appendString:@"\""];
+ return YES;
+}
+
+/**
+ Returns the object represented by the passed-in string or nil on error. The returned object can be
+ a string, number, boolean, null, array or dictionary.
+
+ @param repr the json string to parse
+ @param allowScalar whether to return objects for JSON fragments
+ @param error used to return an error by reference (pass NULL if this is not desired)
+ */
+- (id)objectWithString:(id)repr allowScalar:(BOOL)allowScalar error:(NSError**)error
+{
+
+ if (!repr) {
+ if (error)
+ *error = err(EINPUT, @"Input was 'nil'");
+ return nil;
+ }
+
+ depth = 0;
+ c = [repr UTF8String];
+
+ id o;
+ NSError *err2 = nil;
+ if (![self scanValue:&o error:&err2]) {
+ if (error)
+ *error = err2;
+ return nil;
+ }
+
+ // We found some valid JSON. But did it also contain something else?
+ if (![self scanIsAtEnd]) {
+ if (error)
+ *error = err(ETRAILGARBAGE, @"Garbage after JSON");
+ return nil;
+ }
+
+ // If we don't allow scalars, check that the object we've found is a valid JSON container.
+ if (!allowScalar && ![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) {
+ if (error)
+ *error = err(EFRAGMENT, @"Valid fragment, but not JSON");
+ return nil;
+ }
+
+ NSAssert1(o, @"Should have a valid object from %@", repr);
+ return o;
+}
+
+/**
+ Returns the object represented by the passed-in string or nil on error. The returned object can be
+ a string, number, boolean, null, array or dictionary.
+
+ @param repr the json string to parse
+ @param error used to return an error by reference (pass NULL if this is not desired)
+ */
+- (id)fragmentWithString:(NSString*)repr error:(NSError**)error
+{
+ return [self objectWithString:repr allowScalar:YES error:error];
+}
+
+/**
+ Returns the object represented by the passed-in string or nil on error. The returned object
+ will be either a dictionary or an array.
+
+ @param repr the json string to parse
+ @param error used to return an error by reference (pass NULL if this is not desired)
+ */
+- (id)objectWithString:(NSString*)repr error:(NSError**)error
+{
+ return [self objectWithString:repr allowScalar:NO error:error];
+}
+
+/*
+ In contrast to the public methods, it is an error to omit the error parameter here.
+ */
+- (BOOL)scanValue:(NSObject **)o error:(NSError **)error
+{
+ skipWhitespace(c);
+
+ switch (*c++) {
+ case '{':
+ return [self scanRestOfDictionary:(NSMutableDictionary **)o error:error];
+ break;
+ case '[':
+ return [self scanRestOfArray:(NSMutableArray **)o error:error];
+ break;
+ case '"':
+ return [self scanRestOfString:(NSMutableString **)o error:error];
+ break;
+ case 'f':
+ return [self scanRestOfFalse:(NSNumber **)o error:error];
+ break;
+ case 't':
+ return [self scanRestOfTrue:(NSNumber **)o error:error];
+ break;
+ case 'n':
+ return [self scanRestOfNull:(NSNull **)o error:error];
+ break;
+ case '-':
+ case '0'...'9':
+ c--; // cannot verify number correctly without the first character
+ return [self scanNumber:(NSNumber **)o error:error];
+ break;
+ case '+':
+ *error = err(EPARSENUM, @"Leading + disallowed in number");
+ return NO;
+ break;
+ case 0x0:
+ *error = err(EEOF, @"Unexpected end of string");
+ return NO;
+ break;
+ default:
+ *error = err(EPARSE, @"Unrecognised leading character");
+ return NO;
+ break;
+ }
+
+ NSAssert(0, @"Should never get here");
+ return NO;
+}
+
+- (BOOL)scanRestOfTrue:(NSNumber **)o error:(NSError **)error
+{
+ if (!strncmp(c, "rue", 3)) {
+ c += 3;
+ *o = [NSNumber numberWithBool:YES];
+ return YES;
+ }
+ *error = err(EPARSE, @"Expected 'true'");
+ return NO;
+}
+
+- (BOOL)scanRestOfFalse:(NSNumber **)o error:(NSError **)error
+{
+ if (!strncmp(c, "alse", 4)) {
+ c += 4;
+ *o = [NSNumber numberWithBool:NO];
+ return YES;
+ }
+ *error = err(EPARSE, @"Expected 'false'");
+ return NO;
+}
+
+- (BOOL)scanRestOfNull:(NSNull **)o error:(NSError **)error
+{
+ if (!strncmp(c, "ull", 3)) {
+ c += 3;
+ *o = [NSNull null];
+ return YES;
+ }
+ *error = err(EPARSE, @"Expected 'null'");
+ return NO;
+}
+
+- (BOOL)scanRestOfArray:(NSMutableArray **)o error:(NSError **)error
+{
+ if (maxDepth && ++depth > maxDepth) {
+ *error = err(EDEPTH, @"Nested too deep");
+ return NO;
+ }
+
+ *o = [NSMutableArray arrayWithCapacity:8];
+
+ for (; *c ;) {
+ id v;
+
+ skipWhitespace(c);
+ if (*c == ']' && c++) {
+ depth--;
+ return YES;
+ }
+
+ if (![self scanValue:&v error:error]) {
+ *error = errWithUnderlier(EPARSE, error, @"Expected value while parsing array");
+ return NO;
+ }
+
+ [*o addObject:v];
+
+ skipWhitespace(c);
+ if (*c == ',' && c++) {
+ skipWhitespace(c);
+ if (*c == ']') {
+ *error = err(ETRAILCOMMA, @"Trailing comma disallowed in array");
+ return NO;
+ }
+ }
+ }
+
+ *error = err(EEOF, @"End of input while parsing array");
+ return NO;
+}
+
+- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o error:(NSError **)error
+{
+ if (maxDepth && ++depth > maxDepth) {
+ *error = err(EDEPTH, @"Nested too deep");
+ return NO;
+ }
+
+ *o = [NSMutableDictionary dictionaryWithCapacity:7];
+
+ for (; *c ;) {
+ id k, v;
+
+ skipWhitespace(c);
+ if (*c == '}' && c++) {
+ depth--;
+ return YES;
+ }
+
+ if (!(*c == '\"' && c++ && [self scanRestOfString:&k error:error])) {
+ *error = errWithUnderlier(EPARSE, error, @"Object key string expected");
+ return NO;
+ }
+
+ skipWhitespace(c);
+ if (*c != ':') {
+ *error = err(EPARSE, @"Expected ':' separating key and value");
+ return NO;
+ }
+
+ c++;
+ if (![self scanValue:&v error:error]) {
+ NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k];
+ *error = errWithUnderlier(EPARSE, error, string);
+ return NO;
+ }
+
+ [*o setObject:v forKey:k];
+
+ skipWhitespace(c);
+ if (*c == ',' && c++) {
+ skipWhitespace(c);
+ if (*c == '}') {
+ *error = err(ETRAILCOMMA, @"Trailing comma disallowed in object");
+ return NO;
+ }
+ }
+ }
+
+ *error = err(EEOF, @"End of input while parsing object");
+ return NO;
+}
+
+- (BOOL)scanRestOfString:(NSMutableString **)o error:(NSError **)error
+{
+ *o = [NSMutableString stringWithCapacity:16];
+ do {
+ // First see if there's a portion we can grab in one go.
+ // Doing this caused a massive speedup on the long string.
+ size_t len = strcspn(c, ctrl);
+ if (len) {
+ // check for
+ id t = [[NSString alloc] initWithBytesNoCopy:(char*)c
+ length:len
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ if (t) {
+ [*o appendString:t];
+ [t release];
+ c += len;
+ }
+ }
+
+ if (*c == '"') {
+ c++;
+ return YES;
+
+ }
+ else if (*c == '\\') {
+ unichar uc = *++c;
+ switch (uc) {
+ case '\\':
+ case '/':
+ case '"':
+ break;
+
+ case 'b': uc = '\b'; break;
+ case 'n': uc = '\n'; break;
+ case 'r': uc = '\r'; break;
+ case 't': uc = '\t'; break;
+ case 'f': uc = '\f'; break;
+
+ case 'u':
+ c++;
+ if (![self scanUnicodeChar:&uc error:error]) {
+ *error = errWithUnderlier(EUNICODE, error, @"Broken unicode character");
+ return NO;
+ }
+ c--; // hack.
+ break;
+ default:
+ *error = err(EESCAPE, [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]);
+ return NO;
+ break;
+ }
+ [*o appendFormat:@"%C", uc];
+ c++;
+
+ }
+ else if (*c < 0x20) {
+ *error = err(ECTRL, [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]);
+ return NO;
+
+ }
+ else {
+ NSLog(@"should not be able to get here");
+ }
+ } while (*c);
+
+ *error = err(EEOF, @"Unexpected EOF while parsing string");
+ return NO;
+}
+
+- (BOOL)scanUnicodeChar:(unichar *)x error:(NSError **)error
+{
+ unichar hi, lo;
+
+ if (![self scanHexQuad:&hi error:error]) {
+ *error = err(EUNICODE, @"Missing hex quad");
+ return NO;
+ }
+
+ if (hi >= 0xd800) { // high surrogate char?
+ if (hi < 0xdc00) { // yes - expect a low char
+
+ if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo error:error])) {
+ *error = errWithUnderlier(EUNICODE, error, @"Missing low character in surrogate pair");
+ return NO;
+ }
+
+ if (lo < 0xdc00 || lo >= 0xdfff) {
+ *error = err(EUNICODE, @"Invalid low surrogate char");
+ return NO;
+ }
+
+ hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000;
+
+ }
+ else if (hi < 0xe000) {
+ *error = err(EUNICODE, @"Invalid high character in surrogate pair");
+ return NO;
+ }
+ }
+
+ *x = hi;
+ return YES;
+}
+
+- (BOOL)scanHexQuad:(unichar *)x error:(NSError **)error
+{
+ *x = 0;
+ for (int i = 0; i < 4; i++) {
+ unichar uc = *c;
+ c++;
+ int d = (uc >= '0' && uc <= '9')
+ ? uc - '0' : (uc >= 'a' && uc <= 'f')
+ ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F')
+ ? (uc - 'A' + 10) : -1;
+ if (d == -1) {
+ *error = err(EUNICODE, @"Missing hex digit in quad");
+ return NO;
+ }
+ *x *= 16;
+ *x += d;
+ }
+ return YES;
+}
+
+- (BOOL)scanNumber:(NSNumber **)o error:(NSError **)error
+{
+ const char *ns = c;
+
+ // The logic to test for validity of the number formatting is relicensed
+ // from JSON::XS with permission from its author Marc Lehmann.
+ // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .)
+
+ if ('-' == *c)
+ c++;
+
+ if ('0' == *c && c++) {
+ if (isdigit(*c)) {
+ *error = err(EPARSENUM, @"Leading 0 disallowed in number");
+ return NO;
+ }
+
+ }
+ else if (!isdigit(*c) && c != ns) {
+ *error = err(EPARSENUM, @"No digits after initial minus");
+ return NO;
+
+ }
+ else {
+ skipDigits(c);
+ }
+
+ // Fractional part
+ if ('.' == *c && c++) {
+
+ if (!isdigit(*c)) {
+ *error = err(EPARSENUM, @"No digits after decimal point");
+ return NO;
+ }
+ skipDigits(c);
+ }
+
+ // Exponential part
+ if ('e' == *c || 'E' == *c) {
+ c++;
+
+ if ('-' == *c || '+' == *c)
+ c++;
+
+ if (!isdigit(*c)) {
+ *error = err(EPARSENUM, @"No digits after exponent");
+ return NO;
+ }
+ skipDigits(c);
+ }
+
+ id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns
+ length:c - ns
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ [str autorelease];
+ if (str && (*o = [NSDecimalNumber decimalNumberWithString:str]))
+ return YES;
+
+ *error = err(EPARSENUM, @"Failed creating decimal instance");
+ return NO;
+}
+
+- (BOOL)scanIsAtEnd
+{
+ skipWhitespace(c);
+ return !*c;
+}
+
+@synthesize humanReadable;
+@synthesize sortKeys;
+@synthesize maxDepth;
+
+@end
+
+@implementation NSObject (NSObject_SBJSON)
+
+- (NSString *)JSONFragment
+{
+ SBJSON *generator = [[SBJSON new] autorelease];
+
+ NSError *error;
+ NSString *json = [generator stringWithFragment:self error:&error];
+
+ if (!json)
+ NSLog(@"%@", error);
+ return json;
+}
+
+- (NSString *)JSONRepresentation
+{
+ SBJSON *generator = [[SBJSON new] autorelease];
+
+ NSError *error;
+ NSString *json = [generator stringWithObject:self error:&error];
+
+ if (!json)
+ NSLog(@"%@", error);
+ return json;
+}
+
+@end
+
+@implementation NSString (NSString_SBJSON)
+
+- (id)JSONFragmentValue
+{
+ SBJSON *json = [[SBJSON new] autorelease];
+
+ NSError *error;
+ id o = [json fragmentWithString:self error:&error];
+
+ if (!o)
+ NSLog(@"%@", error);
+ return o;
+}
+
+- (id)JSONValue
+{
+ SBJSON *json = [[SBJSON new] autorelease];
+
+ NSError *error;
+ id o = [json objectWithString:self error:&error];
+
+ if (!o)
+ NSLog(@"%@", error);
+ return o;
+}
+
+@end
10 test/README
@@ -0,0 +1,10 @@
+The directories here contains tests from the following sources:
+
+json.org:
+ http://json.org/example.html
+
+jsonchecker:
+ http://json.org/JSON_checker/
+
+rfc4627:
+ http://www.ietf.org/rfc/rfc4627.txt?number=4627
14 test/format/HumanReadable.json
@@ -0,0 +1,14 @@
+[
+ "one",
+ 2,
+ {
+ "bar" : [
+ 1,
+ 2,
+ []
+ ],
+ "foo" : null,
+ "quux" : true
+ },
+ {}
+]
1  test/format/input.json
@@ -0,0 +1 @@
+["one",2,{"foo":null,"quux":true,"bar":[1, 2, []]},{}]
22 test/json.org/1.json
@@ -0,0 +1,22 @@
+{
+ "glossary": {
+ "title": "example glossary",
+ "GlossDiv": {
+ "title": "S",
+ "GlossList": {
+ "GlossEntry": {
+ "ID": "SGML",
+ "SortAs": "SGML",
+ "GlossTerm": "Standard Generalized Markup Language",
+ "Acronym": "SGML",
+ "Abbrev": "ISO 8879:1986",
+ "GlossDef": {
+ "para": "A meta-markup language, used to create markup languages such as DocBook.",
+ "GlossSeeAlso": ["GML", "XML"]
+ },
+ "GlossSee": "markup"
+ }
+ }
+ }
+ }
+}
44 test/json.org/1.plist
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>glossary</key>
+ <dict>
+ <key>GlossDiv</key>
+ <dict>
+ <key>GlossList</key>
+ <dict>
+ <key>GlossEntry</key>
+ <dict>
+ <key>Abbrev</key>
+ <string>ISO 8879:1986</string>
+ <key>Acronym</key>
+ <string>SGML</string>
+ <key>GlossDef</key>
+ <dict>
+ <key>GlossSeeAlso</key>
+ <array>
+ <string>GML</string>
+ <string>XML</string>
+ </array>
+ <key>para</key>
+ <string>A meta-markup language, used to create markup languages such as DocBook.</string>
+ </dict>
+ <key>GlossSee</key>
+ <string>markup</string>
+ <key>GlossTerm</key>
+ <string>Standard Generalized Markup Language</string>
+ <key>ID</key>
+ <string>SGML</string>
+ <key>SortAs</key>
+ <string>SGML</string>
+ </dict>
+ </dict>
+ <key>title</key>
+ <string>S</string>
+ </dict>
+ <key>title</key>
+ <string>example glossary</string>
+ </dict>
+</dict>
+</plist>
11 test/json.org/2.json
@@ -0,0 +1,11 @@
+{"menu": {
+ "id": "file",
+ "value": "File",
+ "popup": {
+ "menuitem": [
+ {"value": "New", "onclick": "CreateNewDoc()"},
+ {"value": "Open", "onclick": "OpenDoc()"},
+ {"value": "Close", "onclick": "CloseDoc()"}
+ ]
+ }
+}}
37 test/json.org/2.plist
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>menu</key>
+ <dict>
+ <key>id</key>
+ <string>file</string>
+ <key>popup</key>
+ <dict>
+ <key>menuitem</key>
+ <array>
+ <dict>
+ <key>onclick</key>
+ <string>CreateNewDoc()</string>
+ <key>value</key>
+ <string>New</string>
+ </dict>
+ <dict>
+ <key>onclick</key>
+ <string>OpenDoc()</string>
+ <key>value</key>
+ <string>Open</string>
+ </dict>
+ <dict>
+ <key>onclick</key>
+ <string>CloseDoc()</string>
+ <key>value</key>
+ <string>Close</string>
+ </dict>
+ </array>
+ </dict>
+ <key>value</key>
+ <string>File</string>
+ </dict>
+</dict>
+</plist>
26 test/json.org/3.json
@@ -0,0 +1,26 @@
+{"widget": {
+ "debug": "on",
+ "window": {
+ "title": "Sample Konfabulator Widget",
+ "name": "main_window",
+ "width": 500,
+ "height": 500
+ },
+ "image": {
+ "src": "Images/Sun.png",
+ "name": "sun1",
+ "hOffset": 250,
+ "vOffset": 250,
+ "alignment": "center"
+ },
+ "text": {
+ "data": "Click Here",
+ "size": 36,
+ "style": "bold",
+ "name": "text1",
+ "hOffset": 250,
+ "vOffset": 100,
+ "alignment": "center",
+ "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
+ }
+}}
54 test/json.org/3.plist
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>widget</key>
+ <dict>
+ <key>debug</key>
+ <string>on</string>
+ <key>image</key>
+ <dict>
+ <key>alignment</key>
+ <string>center</string>
+ <key>hOffset</key>
+ <real>250</real>
+ <key>name</key>
+ <string>sun1</string>
+ <key>src</key>
+ <string>Images/Sun.png</string>
+ <key>vOffset</key>
+ <real>250</real>
+ </dict>
+ <key>text</key>
+ <dict>
+ <key>alignment</key>
+ <string>center</string>
+ <key>data</key>
+ <string>Click Here</string>
+ <key>hOffset</key>
+ <real>250</real>
+ <key>name</key>
+ <string>text1</string>
+ <key>onMouseUp</key>
+ <string>sun1.opacity = (sun1.opacity / 100) * 90;</string>
+ <key>size</key>
+ <real>36</real>
+ <key>style</key>
+ <string>bold</string>
+ <key>vOffset</key>
+ <real>100</real>
+ </dict>
+ <key>window</key>
+ <dict>
+ <key>height</key>
+ <real>500</real>
+ <key>name</key>
+ <string>main_window</string>
+ <key>title</key>
+ <string>Sample Konfabulator Widget</string>
+ <key>width</key>
+ <real>500</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
88 test/json.org/4.json
@@ -0,0 +1,88 @@
+{"web-app": {
+ "servlet": [
+ {
+ "servlet-name": "cofaxCDS",
+ "servlet-class": "org.cofax.cds.CDSServlet",
+ "init-param": {
+ "configGlossary:installationAt": "Philadelphia, PA",
+ "configGlossary:adminEmail": "ksm@pobox.com",
+ "configGlossary:poweredBy": "Cofax",
+ "configGlossary:poweredByIcon": "/images/cofax.gif",
+ "configGlossary:staticPath": "/content/static",
+ "templateProcessorClass": "org.cofax.WysiwygTemplate",
+ "templateLoaderClass": "org.cofax.FilesTemplateLoader",
+ "templatePath": "templates",
+ "templateOverridePath": "",
+ "defaultListTemplate": "listTemplate.htm",
+ "defaultFileTemplate": "articleTemplate.htm",
+ "useJSP": false,
+ "jspListTemplate": "listTemplate.jsp",
+ "jspFileTemplate": "articleTemplate.jsp",
+ "cachePackageTagsTrack": 200,
+ "cachePackageTagsStore": 200,
+ "cachePackageTagsRefresh": 60,
+ "cacheTemplatesTrack": 100,
+ "cacheTemplatesStore": 50,
+ "cacheTemplatesRefresh": 15,
+ "cachePagesTrack": 200,
+ "cachePagesStore": 100,
+ "cachePagesRefresh": 10,
+ "cachePagesDirtyRead": 10,
+ "searchEngineListTemplate": "forSearchEnginesList.htm",
+ "searchEngineFileTemplate": "forSearchEngines.htm",
+ "searchEngineRobotsDb": "WEB-INF/robots.db",
+ "useDataStore": true,
+ "dataStoreClass": "org.cofax.SqlDataStore",
+ "redirectionClass": "org.cofax.SqlRedirection",
+ "dataStoreName": "cofax",
+ "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
+ "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
+ "dataStoreUser": "sa",
+ "dataStorePassword": "dataStoreTestQuery",
+ "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
+ "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
+ "dataStoreInitConns": 10,
+ "dataStoreMaxConns": 100,
+ "dataStoreConnUsageLimit": 100,
+ "dataStoreLogLevel": "debug",
+ "maxUrlLength": 500}},
+ {
+ "servlet-name": "cofaxEmail",
+ "servlet-class": "org.cofax.cds.EmailServlet",
+ "init-param": {
+ "mailHost": "mail1",
+ "mailHostOverride": "mail2"}},
+ {
+ "servlet-name": "cofaxAdmin",
+ "servlet-class": "org.cofax.cds.AdminServlet"},
+
+ {
+ "servlet-name": "fileServlet",
+ "servlet-class": "org.cofax.cds.FileServlet"},
+ {
+ "servlet-name": "cofaxTools",
+ "servlet-class": "org.cofax.cms.CofaxToolsServlet",
+ "init-param": {
+ "templatePath": "toolstemplates/",
+ "log": 1,
+ "logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
+ "logMaxSize": "",
+ "dataLog": 1,
+ "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
+ "dataLogMaxSize": "",
+ "removePageCache": "/content/admin/remove?cache=pages&id=",
+ "removeTemplateCache": "/content/admin/remove?cache=templates&id=",
+ "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
+ "lookInContext": 1,
+ "adminGroupID": 4,
+ "betaServer": true}}],
+ "servlet-mapping": {
+ "cofaxCDS": "/",
+ "cofaxEmail": "/cofaxutil/aemail/*",
+ "cofaxAdmin": "/admin/*",
+ "fileServlet": "/static/*",
+ "cofaxTools": "/tools/*"},
+
+ "taglib": {
+ "taglib-uri": "cofax.tld",
+ "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}
185 test/json.org/4.plist
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>web-app</key>
+ <dict>
+ <key>servlet</key>
+ <array>
+ <dict>
+ <key>init-param</key>
+ <dict>
+ <key>cachePackageTagsRefresh</key>
+ <real>60</real>
+ <key>cachePackageTagsStore</key>
+ <real>200</real>
+ <key>cachePackageTagsTrack</key>
+ <real>200</real>
+ <key>cachePagesDirtyRead</key>
+ <real>10</real>
+ <key>cachePagesRefresh</key>
+ <real>10</real>
+ <key>cachePagesStore</key>
+ <real>100</real>
+ <key>cachePagesTrack</key>
+ <real>200</real>
+ <key>cacheTemplatesRefresh</key>
+ <real>15</real>
+ <key>cacheTemplatesStore</key>
+ <real>50</real>
+ <key>cacheTemplatesTrack</key>
+ <real>100</real>
+ <key>configGlossary:adminEmail</key>
+ <string>ksm@pobox.com</string>
+ <key>configGlossary:installationAt</key>
+ <string>Philadelphia, PA</string>
+ <key>configGlossary:poweredBy</key>
+ <string>Cofax</string>
+ <key>configGlossary:poweredByIcon</key>
+ <string>/images/cofax.gif</string>
+ <key>configGlossary:staticPath</key>
+ <string>/content/static</string>
+ <key>dataStoreClass</key>
+ <string>org.cofax.SqlDataStore</string>
+ <key>dataStoreConnUsageLimit</key>
+ <real>100</real>
+ <key>dataStoreDriver</key>
+ <string>com.microsoft.jdbc.sqlserver.SQLServerDriver</string>
+ <key>dataStoreInitConns</key>
+ <real>10</real>
+ <key>dataStoreLogFile</key>
+ <string>/usr/local/tomcat/logs/datastore.log</string>
+ <key>dataStoreLogLevel</key>
+ <string>debug</string>
+ <key>dataStoreMaxConns</key>
+ <real>100</real>
+ <key>dataStoreName</key>
+ <string>cofax</string>
+ <key>dataStorePassword</key>
+ <string>dataStoreTestQuery</string>
+ <key>dataStoreTestQuery</key>
+ <string>SET NOCOUNT ON;select test='test';</string>
+ <key>dataStoreUrl</key>
+ <string>jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon</string>
+ <key>dataStoreUser</key>
+ <string>sa</string>
+ <key>defaultFileTemplate</key>
+ <string>articleTemplate.htm</string>
+ <key>defaultListTemplate</key>
+ <string>listTemplate.htm</string>
+ <key>jspFileTemplate</key>
+ <string>articleTemplate.jsp</string>
+ <key>jspListTemplate</key>
+ <string>listTemplate.jsp</string>
+ <key>maxUrlLength</key>
+ <real>500</real>
+ <key>redirectionClass</key>
+ <string>org.cofax.SqlRedirection</string>
+ <key>searchEngineFileTemplate</key>
+ <string>forSearchEngines.htm</string>
+ <key>searchEngineListTemplate</key>
+ <string>forSearchEnginesList.htm</string>
+ <key>searchEngineRobotsDb</key>
+ <string>WEB-INF/robots.db</string>
+ <key>templateLoaderClass</key>
+ <string>org.cofax.FilesTemplateLoader</string>
+ <key>templateOverridePath</key>
+ <string></string>
+ <key>templatePath</key>
+ <string>templates</string>
+ <key>templateProcessorClass</key>
+ <string>org.cofax.WysiwygTemplate</string>
+ <key>useDataStore</key>
+ <true/>
+ <key>useJSP</key>
+ <false/>
+ </dict>
+ <key>servlet-class</key>
+ <string>org.cofax.cds.CDSServlet</string>
+ <key>servlet-name</key>
+ <string>cofaxCDS</string>
+ </dict>
+ <dict>
+ <key>init-param</key>
+ <dict>
+ <key>mailHost</key>
+ <string>mail1</string>
+ <key>mailHostOverride</key>
+ <string>mail2</string>
+ </dict>
+ <key>servlet-class</key>
+ <string>org.cofax.cds.EmailServlet</string>
+ <key>servlet-name</key>
+ <string>cofaxEmail</string>
+ </dict>
+ <dict>
+ <key>servlet-class</key>
+ <string>org.cofax.cds.AdminServlet</string>
+ <key>servlet-name</key>
+ <string>cofaxAdmin</string>
+ </dict>
+ <dict>
+ <key>servlet-class</key>
+ <string>org.cofax.cds.FileServlet</string>
+ <key>servlet-name</key>
+ <string>fileServlet</string>
+ </dict>
+ <dict>
+ <key>init-param</key>
+ <dict>
+ <key>adminGroupID</key>
+ <real>4</real>
+ <key>betaServer</key>
+ <true/>
+ <key>dataLog</key>
+ <real>1</real>
+ <key>dataLogLocation</key>
+ <string>/usr/local/tomcat/logs/dataLog.log</string>
+ <key>dataLogMaxSize</key>
+ <string></string>
+ <key>fileTransferFolder</key>
+ <string>/usr/local/tomcat/webapps/content/fileTransferFolder</string>
+ <key>log</key>
+ <real>1</real>
+ <key>logLocation</key>
+ <string>/usr/local/tomcat/logs/CofaxTools.log</string>
+ <key>logMaxSize</key>
+ <string></string>
+ <key>lookInContext</key>
+ <real>1</real>
+ <key>removePageCache</key>
+ <string>/content/admin/remove?cache=pages&amp;id=</string>
+ <key>removeTemplateCache</key>
+ <string>/content/admin/remove?cache=templates&amp;id=</string>
+ <key>templatePath</key>
+ <string>toolstemplates/</string>
+ </dict>
+ <key>servlet-class</key>
+ <string>org.cofax.cms.CofaxToolsServlet</string>
+ <key>servlet-name</key>
+ <string>cofaxTools</string>
+ </dict>
+ </array>
+ <key>servlet-mapping</key>
+ <dict>
+ <key>cofaxAdmin</key>
+ <string>/admin/*</string>
+ <key>cofaxCDS</key>
+ <string>/</string>
+ <key>cofaxEmail</key>
+ <string>/cofaxutil/aemail/*</string>
+ <key>cofaxTools</key>
+ <string>/tools/*</string>
+ <key>fileServlet</key>
+ <string>/static/*</string>
+ </dict>
+ <key>taglib</key>
+ <dict>
+ <key>taglib-location</key>
+ <string>/WEB-INF/tlds/cofax.tld</string>
+ <key>taglib-uri</key>
+ <string>cofax.tld</string>
+ </dict>
+ </dict>
+</dict>
+</plist>
27 test/json.org/5.json
@@ -0,0 +1,27 @@
+{"menu": {
+ "header": "SVG Viewer",
+ "items": [
+ {"id": "Open"},
+ {"id": "OpenNew", "label": "Open New"},
+ null,
+ {"id": "ZoomIn", "label": "Zoom In"},
+ {"id": "ZoomOut", "label": "Zoom Out"},
+ {"id": "OriginalView", "label": "Original View"},
+ null,
+ {"id": "Quality"},
+ {"id": "Pause"},
+ {"id": "Mute"},
+ null,
+ {"id": "Find", "label": "Find..."},
+ {"id": "FindAgain", "label": "Find Again"},
+ {"id": "Copy"},
+ {"id": "CopyAgain", "label": "Copy Again"},
+ {"id": "CopySVG", "label": "Copy SVG"},
+ {"id": "ViewSVG", "label": "View SVG"},
+ {"id": "ViewSource", "label": "View Source"},
+ {"id": "SaveAs", "label": "Save As"},
+ null,
+ {"id": "Help"},
+ {"id": "About", "label": "About Adobe CVG Viewer..."}
+ ]
+}}
1  test/jsonchecker/fail1.json
@@ -0,0 +1 @@
+"A JSON payload should be an object or array, not a string."
1  test/jsonchecker/fail10.json
@@ -0,0 +1 @@
+{"Extra value after close": true} "misplaced quoted value"
1  test/jsonchecker/fail11.json
@@ -0,0 +1 @@
+{"Illegal expression": 1 + 2}
1  test/jsonchecker/fail12.json
@@ -0,0 +1 @@
+{"Illegal invocation": alert()}
1  test/jsonchecker/fail13.json
@@ -0,0 +1 @@
+{"Numbers cannot have leading zeroes": 013}
1  test/jsonchecker/fail14.json
@@ -0,0 +1 @@
+{"Numbers cannot be hex": 0x14}
1  test/jsonchecker/fail15.json
@@ -0,0 +1 @@
+["Illegal backslash escape: \x15"]
1  test/jsonchecker/fail16.json
@@ -0,0 +1 @@
+[\naked]
1  test/jsonchecker/fail17.json
@@ -0,0 +1 @@
+["Illegal backslash escape: \017"]
1  test/jsonchecker/fail18.json
@@ -0,0 +1 @@
+[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]
1  test/jsonchecker/fail19.json
@@ -0,0 +1 @@
+{"Missing colon" null}
1  test/jsonchecker/fail2.json
@@ -0,0 +1 @@
+["Unclosed array"
1  test/jsonchecker/fail20.json
@@ -0,0 +1 @@
+{"Double colon":: null}
1  test/jsonchecker/fail21.json
@@ -0,0 +1 @@
+{"Comma instead of colon", null}
1  test/jsonchecker/fail22.json
@@ -0,0 +1 @@
+["Colon instead of comma": false]
1  test/jsonchecker/fail23.json
@@ -0,0 +1 @@
+["Bad value", truth]
1  test/jsonchecker/fail24.json
@@ -0,0 +1 @@
+['single quote']
1  test/jsonchecker/fail25.json
@@ -0,0 +1 @@
+[" tab character in string "]
1  test/jsonchecker/fail26.json
@@ -0,0 +1 @@
+["tab\ character\ in\ string\ "]
2  test/jsonchecker/fail27.json
@@ -0,0 +1,2 @@
+["line
+break"]
2  test/jsonchecker/fail28.json
@@ -0,0 +1,2 @@
+["line\
+break"]
1  test/jsonchecker/fail29.json
@@ -0,0 +1 @@
+[0e]
1  test/jsonchecker/fail3.json
@@ -0,0 +1 @@
+{unquoted_key: "keys must be quoted"}
1  test/jsonchecker/fail30.json
@@ -0,0 +1 @@
+[0e+]
1  test/jsonchecker/fail31.json
@@ -0,0 +1 @@
+[0e+-1]
1  test/jsonchecker/fail32.json
@@ -0,0 +1 @@
+{"Comma instead if closing brace": true,
1  test/jsonchecker/fail33.json
@@ -0,0 +1 @@
+["mismatch"}
1  test/jsonchecker/fail4.json
@@ -0,0 +1 @@
+["extra comma",]
1  test/jsonchecker/fail5.json
@@ -0,0 +1 @@
+["double extra comma",,]
1  test/jsonchecker/fail6.json
@@ -0,0 +1 @@
+[ , "<-- missing value"]
1  test/jsonchecker/fail7.json
@@ -0,0 +1 @@
+["Comma after the close"],
1  test/jsonchecker/fail8.json
@@ -0,0 +1 @@
+["Extra close"]]
1  test/jsonchecker/fail9.json
@@ -0,0 +1 @@
+{"Extra comma": true,}
58 test/jsonchecker/pass1.json
@@ -0,0 +1,58 @@
+[
+ "JSON Test Pattern pass1",
+ {"object with 1 member":["array with 1 element"]},
+ {},
+ [],
+ -42,
+ true,
+ false,
+ null,
+ {
+ "integer": 1234567890,
+ "real": -9876.543210,
+ "e": 0.123456789e-12,
+ "E": 1.234567890E+34,
+ "": 23456789012E66,
+ "zero": 0,
+ "one": 1,
+ "space": " ",
+ "quote": "\"",
+ "backslash": "\\",
+ "controls": "\b\f\n\r\t",
+ "slash": "/ & \/",
+ "alpha": "abcdefghijklmnopqrstuvwyz",
+ "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+ "digit": "0123456789",
+ "0123456789": "digit",
+ "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+ "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+ "true": true,
+ "false": false,
+ "null": null,
+ "array":[ ],
+ "object":{ },
+ "address": "50 St. James Street",
+ "url": "http://www.JSON.org/",
+ "comment": "// /* <!-- --",
+ "# -- --> */": " ",
+ " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
+ "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+ "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+ "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+ },
+ 0.5 ,98.6
+,
+99.44
+,
+
+1066,
+1e1,
+0.1e1,
+1e-1,
+1e00,2e+00,2e-00
+,"rosebud"]
1  test/jsonchecker/pass2.json
@@ -0,0 +1 @@
+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
6 test/jsonchecker/pass3.json
@@ -0,0 +1,6 @@
+{
+ "JSON Test Pattern pass3": {
+ "The outermost value": "must be an object or array.",
+ "In this test": "It is an object."
+ }
+}
16 test/original/Errors.h
@@ -0,0 +1,16 @@
+//
+// Tests.h
+// JSON
+//
+// Created by Stig Brautaset on 11/09/2007.
+// Copyright 2007 Stig Brautaset. All rights reserved.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+
+@class SBJSON;
+
+@interface Errors : SenTestCase {
+ SBJSON *json;
+}
+@end
269 test/original/Errors.m
@@ -0,0 +1,269 @@
+//
+// Errors.m
+// JSON
+//
+// Created by Stig Brautaset on 13/09/2007.
+// Copyright 2007 Stig Brautaset. All rights reserved.
+//
+
+#import "Errors.h"
+#import <JSON/JSON.h>
+
+// The ST guys sure like typing. Personally, I don't.
+#define tn(expr, name) \
+ STAssertThrowsSpecificNamed(expr, NSException, name, @"ieee!")
+
+#define assertErrorContains(e, s) \
+ STAssertTrue([[e localizedDescription] hasPrefix:s], @"%@", [e userInfo])
+
+#define assertUnderlyingErrorContains(e, s) \
+ STAssertTrue([[[[e userInfo] objectForKey:NSUnderlyingErrorKey] localizedDescription] hasPrefix:s], @"%@", [e userInfo])
+
+@implementation Errors
+
+- (void)setUp {
+ json = [SBJSON new];
+}
+
+- (void)tearDown {
+ [json release];
+}
+
+#pragma mark Generator
+
+- (void)testUnsupportedObject
+{
+ NSError *error = nil;
+ STAssertNil([json stringWithObject:[NSDate date] error:&error], nil);
+ STAssertNotNil(error, nil);
+}
+
+- (void)testNonStringDictionaryKey
+{
+ NSArray *keys = [NSArray arrayWithObjects:[NSNull null],
+ [NSNumber numberWithInt:1],
+ [NSArray array],
+ [NSDictionary dictionary],
+ nil];
+
+ for (int i = 0; i < [keys count]; i++) {
+ NSError *error = nil;
+ NSDictionary *object = [NSDictionary dictionaryWithObject:@"1" forKey:[keys objectAtIndex:i]];
+ STAssertNil([json stringWithObject:object error:&error], nil);
+ STAssertNotNil(error, nil);
+ }
+}
+
+
+- (void)testScalar
+{
+ NSArray *fragments = [NSArray arrayWithObjects:@"foo", @"", [NSNull null], [NSNumber numberWithInt:1], [NSNumber numberWithBool:YES], nil];
+ for (int i = 0; i < [fragments count]; i++) {
+ NSString *fragment = [fragments objectAtIndex:i];
+
+ // We don't check the convenience category here, like we do for parsing,
+ // because the category is explicitly on the NSArray and NSDictionary objects.
+ // STAssertNil([fragment JSONRepresentation], nil);
+
+ NSError *error = nil;
+ STAssertNil([json stringWithObject:fragment error:&error], @"%@", fragment);
+ assertErrorContains(error, @"Not valid type for JSON");
+ }
+}
+
+
+#pragma mark Scanner
+
+- (void)testArray {
+ NSError *error;
+
+ STAssertNil([json objectWithString:@"[1,,2]" error:&error], nil);
+ assertErrorContains(error, @"Expected value");
+
+ STAssertNil([json objectWithString:@"[1,,]" error:&error], nil);
+ assertErrorContains(error, @"Expected value");
+
+ STAssertNil([json objectWithString:@"[,1]" error:&error], nil);
+ assertErrorContains(error, @"Expected value");
+
+
+ STAssertNil([json objectWithString:@"[1,]" error:&error], nil);
+ assertErrorContains(error, @"Trailing comma disallowed");
+
+
+ STAssertNil([json objectWithString:@"[1" error:&error], nil);
+ assertErrorContains(error, @"End of input while parsing array");
+
+ STAssertNil([json objectWithString:@"[[]" error:&error], nil);
+ assertErrorContains(error, @"End of input while parsing array");
+
+ // See if seemingly-valid arrays have nasty elements
+ STAssertNil([json objectWithString:@"[+1]" error:&error], nil);
+ assertErrorContains(error, @"Expected value");
+ assertUnderlyingErrorContains(error, @"Leading + disallowed");
+}
+
+- (void)testObject {
+ NSError *error;
+
+ STAssertNil([json objectWithString:@"{1" error:&error], nil);
+ assertErrorContains(error, @"Object key string expected");
+
+ STAssertNil([json objectWithString:@"{null" error:&error], nil);
+ assertErrorContains(error, @"Object key string expected");
+
+ STAssertNil([json objectWithString:@"{\"a\":1,,}" error:&error], nil);
+ assertErrorContains(error, @"Object key string expected");
+
+ STAssertNil([json objectWithString:@"{,\"a\":1}" error:&error], nil);
+ assertErrorContains(error, @"Object key string expected");
+
+
+ STAssertNil([json objectWithString:@"{\"a\"" error:&error], nil);
+ assertErrorContains(error, @"Expected ':'");
+
+
+ STAssertNil([json objectWithString:@"{\"a\":" error:&error], nil);
+ assertErrorContains(error, @"Object value expected");
+
+ STAssertNil([json objectWithString:@"{\"a\":," error:&error], nil);
+ assertErrorContains(error, @"Object value expected");
+
+
+ STAssertNil([json objectWithString:@"{\"a\":1,}" error:&error], nil);
+ assertErrorContains(error, @"Trailing comma disallowed");
+
+
+ STAssertNil([json objectWithString:@"{" error:&error], nil);
+ assertErrorContains(error, @"End of input while parsing object");
+
+ STAssertNil([json objectWithString:@"{\"a\":{}" error:&error], nil);
+ assertErrorContains(error, @"End of input while parsing object");
+}
+
+- (void)testNumber {
+ NSError *error;
+
+ STAssertNil([json fragmentWithString:@"-" error:&error], nil);
+ assertErrorContains(error, @"No digits after initial minus");
+
+ STAssertNil([json fragmentWithString:@"+1" error:&error], nil);
+ assertErrorContains(error, @"Leading + disallowed in number");
+
+ STAssertNil([json fragmentWithString:@"01" error:&error], nil);
+ assertErrorContains(error, @"Leading 0 disallowed in number");
+
+ STAssertNil([json fragmentWithString:@"0." error:&error], nil);
+ assertErrorContains(error, @"No digits after decimal point");
+
+
+ STAssertNil([json fragmentWithString:@"1e" error:&error], nil);
+ assertErrorContains(error, @"No digits after exponent");
+
+ STAssertNil([json fragmentWithString:@"1e-" error:&error], nil);
+ assertErrorContains(error, @"No digits after exponent");
+
+ STAssertNil([json fragmentWithString:@"1e+" error:&error], nil);
+ assertErrorContains(error, @"No digits after exponent");
+}
+
+- (void)testNull {
+ NSError *error;
+
+ STAssertNil([json fragmentWithString:@"nil" error:&error], nil);
+ assertErrorContains(error, @"Expected 'null'");
+}
+
+- (void)testBool {
+ NSError *error;
+
+ STAssertNil([json fragmentWithString:@"truth" error:&error], nil);
+ assertErrorContains(error, @"Expected 'true'");
+
+ STAssertNil([json fragmentWithString:@"fake" error:&error], nil);
+ assertErrorContains(error, @"Expected 'false'");
+}
+
+- (void)testString {
+ NSError *error;
+
+ STAssertNil([json fragmentWithString:@"" error:&error], nil);
+ assertErrorContains(error, @"Unexpected end of string");
+
+ STAssertNil([json objectWithString:@"" error:&error], nil);
+ assertErrorContains(error, @"Unexpected end of string");
+
+ STAssertNil([json fragmentWithString:@"\"" error:&error], nil);
+ assertErrorContains(error, @"Unescaped control character");
+
+ STAssertNil([json fragmentWithString:@"\"foo" error:&error], nil);
+ assertErrorContains(error, @"Unescaped control character");
+
+
+ STAssertNil([json fragmentWithString:@"\"\\uD834foo\"" error:&error], nil);
+ assertErrorContains(error, @"Broken unicode character");
+ assertUnderlyingErrorContains(error, @"Missing low character");
+
+ STAssertNil([json fragmentWithString:@"\"\\uD834\\u001E\"" error:&error], nil);
+ assertErrorContains(error, @"Broken unicode character");
+ assertUnderlyingErrorContains(error, @"Invalid low surrogate");
+
+ STAssertNil([json fragmentWithString:@"\"\\uDD1Ef\"" error:&error], nil);
+ assertErrorContains(error, @"Broken unicode character");
+ assertUnderlyingErrorContains(error, @"Invalid high character");
+
+
+ for (NSUInteger i = 0; i < 0x20; i++) {
+ NSString *str = [NSString stringWithFormat:@"\"%C\"", i];
+ STAssertNil([json fragmentWithString:str error:&error], nil);
+ assertErrorContains(error, @"Unescaped control character");
+ }
+}
+
+- (void)testObjectGarbage {
+ NSError *error;
+
+ STAssertNil([json objectWithString:@"'1'" error:&error], nil);
+ assertErrorContains(error, @"Unrecognised leading character");
+
+ STAssertNil([json objectWithString:@"'hello'" error:&error], nil);
+ assertErrorContains(error, @"Unrecognised leading character");
+
+ STAssertNil([json objectWithString:@"**" error:&error], nil);
+ assertErrorContains(error, @"Unrecognised leading character");
+
+ STAssertNil([json objectWithString:nil error:&error], nil);
+ assertErrorContains(error, @"Input was 'nil'");
+}
+
+- (void)testFragmentGarbage {
+ NSError *error;
+
+ STAssertNil([json fragmentWithString:@"'1'" error:&error], nil);
+ assertErrorContains(error, @"Unrecognised leading character");
+
+ STAssertNil([json fragmentWithString:@"'hello'" error:&error], nil);
+ assertErrorContains(error, @"Unrecognised leading character");
+
+ STAssertNil([json fragmentWithString:@"**" error:&error], nil);
+ assertErrorContains(error, @"Unrecognised leading character");
+
+ STAssertNil([json fragmentWithString:nil error:&error], nil);
+ assertErrorContains(error, @"Input was 'nil'");
+}
+
+- (void)testFragment
+{
+ NSArray *fragments = [@"true false null 1 1.0 \"str\"" componentsSeparatedByString:@" "];
+ for (int i = 0; i < [fragments count]; i++) {
+ NSString *fragment = [fragments objectAtIndex:i];
+
+ STAssertNil([fragment JSONValue], nil);
+
+ NSError *error;
+ STAssertNil([json objectWithString:fragment error:&error], fragment);
+ assertErrorContains(error, @"Valid fragment");
+ }
+}
+
+@end
12 test/original/Pretty.h
@@ -0,0 +1,12 @@
+//
+// Tests.h
+// JSON
+//
+// Created by Stig Brautaset on 11/09/2007.
+// Copyright 2007 Stig Brautaset. All rights reserved.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+
+@interface Pretty : SenTestCase
+@end
42 test/original/Pretty.m
@@ -0,0 +1,42 @@
+//
+// Pretty.m
+// JSON
+//
+// Created by Stig Brautaset on 26/09/2007.
+// Copyright 2007 Stig Brautaset. All rights reserved.
+//
+
+#import "Pretty.h"
+#import <JSON/JSON.h>
+
+@implementation Pretty
+