Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first commit

  • Loading branch information...
commit dfdde767a3647f496cc99eb29e978bf24b0216f0 0 parents
@ohsc authored
Showing with 4,510 additions and 0 deletions.
  1. +11 −0 .gitignore
  2. +24 −0 LICENSE
  3. +42 −0 README.md
  4. +10 −0 SBJson/JSON.h
  5. +67 −0 SBJson/NSObject+SBJson.h
  6. +58 −0 SBJson/NSObject+SBJson.m
  7. +84 −0 SBJson/SBJson.h
  8. +107 −0 SBJson/SBJsonParser.h
  9. +104 −0 SBJson/SBJsonParser.m
  10. +167 −0 SBJson/SBJsonStreamParser.h
  11. +251 −0 SBJson/SBJsonStreamParser.m
  12. +40 −0 SBJson/SBJsonStreamParserAccumulator.h
  13. +51 −0 SBJson/SBJsonStreamParserAccumulator.m
  14. +148 −0 SBJson/SBJsonStreamParserAdapter.h
  15. +171 −0 SBJson/SBJsonStreamParserAdapter.m
  16. +81 −0 SBJson/SBJsonStreamParserState.h
  17. +347 −0 SBJson/SBJsonStreamParserState.m
  18. +194 −0 SBJson/SBJsonStreamWriter.h
  19. +375 −0 SBJson/SBJsonStreamWriter.m
  20. +39 −0 SBJson/SBJsonStreamWriterAccumulator.h
  21. +56 −0 SBJson/SBJsonStreamWriterAccumulator.m
  22. +69 −0 SBJson/SBJsonStreamWriterState.h
  23. +139 −0 SBJson/SBJsonStreamWriterState.m
  24. +70 −0 SBJson/SBJsonTokeniser.h
  25. +463 −0 SBJson/SBJsonTokeniser.m
  26. +59 −0 SBJson/SBJsonUTF8Stream.h
  27. +143 −0 SBJson/SBJsonUTF8Stream.m
  28. +115 −0 SBJson/SBJsonWriter.h
  29. +113 −0 SBJson/SBJsonWriter.m
  30. +377 −0 TGJSBridge.xcodeproj/project.pbxproj
  31. +21 −0 TGJSBridge/TGAppDelegate.h
  32. +92 −0 TGJSBridge/TGAppDelegate.m
  33. +45 −0 TGJSBridge/TGJSBridge-Info.plist
  34. +14 −0 TGJSBridge/TGJSBridge-Prefix.pch
  35. +35 −0 TGJSBridge/TGJSBridge/TGJSBridge.h
  36. +85 −0 TGJSBridge/TGJSBridge/TGJSBridge.js
  37. +188 −0 TGJSBridge/TGJSBridge/TGJSBridge.m
  38. +2 −0  TGJSBridge/en.lproj/InfoPlist.strings
  39. +18 −0 TGJSBridge/main.m
  40. +35 −0 TGJSBridge/test.html
11 .gitignore
@@ -0,0 +1,11 @@
+# Mac OS X
+*.DS_Store
+
+# Xcode
+*.pbxuser
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+*.xcuserstate
+project.xcworkspace
+xcuserdata
24 LICENSE
@@ -0,0 +1,24 @@
+Copyright (C) 2012 Chao Shen. 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.
42 README.md
@@ -0,0 +1,42 @@
+TGJSBridge
+=============
+TGJSBridge is a lightweight javascript bridge to cocoa.
+TGJSBridge is iOS4 and iPad compatible and released under the BSD license(see TGJSBridge.h)
+
+Usage in objective-c
+----------------------
+### Init jsBridge
+
+ TGJSBridge *jsBridge = [TGJSBridge jsBridgeWithDelegate: webViewDelegate];
+ webView.delegate = jsBridge;
+
+
+### Send notification to javascript
+`postNotificationName:userInfo:toWebView:`
+
+### Listen notification from javascript
+
+ - (void)jsBridge:(TGJSBridge *)bridge didReceivedNotificationName:(NSString *)name userInfo:(NSDictionary *)userInfo fromWebView:(UIWebView *)webview
+
+Usage in webview
+----------------------
+### Send notification to cocoa
+
+ jsBridge.postNotification(msgName,userInfo);
+
+### Listen notification from cocoa
+
+ jsBridge.bind(msgName, function(userInfo){
+ ...
+ });
+
+### Cancel listening notification from cocoa
+
+ jsBridge.unbind('test',callbackHandler);
+
+LICENSE
+----------------------
+Copyright (c) 2012 Chao Shen(Hangzhou Jiuyan Technology Co., Ltd.). This software is licensed under the BSD License.
+
+
+
10 SBJson/JSON.h
@@ -0,0 +1,10 @@
+//
+// JSON.h
+// SBJson
+//
+// Created by Stig Brautaset on 01/06/2011.
+// Copyright 2011 Stig Brautaset. All rights reserved.
+//
+
+#warning The JSON.h header is deprecated, and will disappear in a future release. Please change to include SBJson.h instead.
+#include "SBJson.h"
67 SBJson/NSObject+SBJson.h
@@ -0,0 +1,67 @@
+/*
+ Copyright (C) 2009 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 <Foundation/Foundation.h>
+
+#pragma mark JSON Writing
+
+/// Adds JSON generation to NSObject
+@interface NSObject (NSObject_SBJsonWriting)
+
+/**
+ @brief Encodes the receiver into a JSON string
+
+ Although defined as a category on NSObject it is only defined for NSArray and NSDictionary.
+
+ @return the receiver encoded in JSON, or nil on error.
+
+ @see @ref objc2json
+ */
+- (NSString *)JSONRepresentation;
+
+@end
+
+
+#pragma mark JSON Parsing
+
+/// Adds JSON parsing methods to NSString
+@interface NSString (NSString_SBJsonParsing)
+
+/**
+ @brief Decodes the receiver's JSON text
+
+ @return the NSDictionary or NSArray represented by the receiver, or nil on error.
+
+ @see @ref json2objc
+ */
+- (id)JSONValue;
+
+@end
+
+
58 SBJson/NSObject+SBJson.m
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2009 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 "NSObject+SBJson.h"
+#import "SBJsonWriter.h"
+#import "SBJsonParser.h"
+
+@implementation NSObject (NSObject_SBJsonWriting)
+
+- (NSString *)JSONRepresentation {
+ SBJsonWriter *writer = [[[SBJsonWriter alloc] init] autorelease];
+ NSString *json = [writer stringWithObject:self];
+ if (!json)
+ NSLog(@"-JSONRepresentation failed. Error is: %@", writer.error);
+ return json;
+}
+
+@end
+
+
+
+@implementation NSString (NSString_SBJsonParsing)
+
+- (id)JSONValue {
+ SBJsonParser *parser = [[[SBJsonParser alloc] init] autorelease];
+ id repr = [parser objectWithString:self];
+ if (!repr)
+ NSLog(@"-JSONValue failed. Error is: %@", parser.error);
+ return repr;
+}
+
+@end
84 SBJson/SBJson.h
@@ -0,0 +1,84 @@
+/*
+ Copyright (C) 2009-2011 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.
+ */
+
+/**
+ @page json2objc JSON to Objective-C
+
+ JSON is mapped to Objective-C types in the following way:
+
+ @li null -> NSNull
+ @li string -> NSString
+ @li array -> NSMutableArray
+ @li object -> NSMutableDictionary
+ @li true -> NSNumber's -numberWithBool:YES
+ @li false -> NSNumber's -numberWithBool:NO
+ @li integer up to 19 digits -> NSNumber's -numberWithLongLong:
+ @li all other numbers -> NSDecimalNumber
+
+ Since Objective-C doesn't have a dedicated class for boolean values,
+ these turns into NSNumber instances. However, since these are
+ initialised with the -initWithBool: method they round-trip back to JSON
+ properly. In other words, they won't silently suddenly become 0 or 1;
+ they'll be represented as 'true' and 'false' again.
+
+ As an optimisation integers up to 19 digits in length (the max length
+ for signed long long integers) turn into NSNumber instances, while
+ complex ones turn into NSDecimalNumber instances. We can thus avoid any
+ loss of precision as JSON allows ridiculously large numbers.
+
+ @page objc2json Objective-C to JSON
+
+ Objective-C types are mapped to JSON types in the following way:
+
+ @li NSNull -> null
+ @li NSString -> string
+ @li NSArray -> array
+ @li NSDictionary -> object
+ @li NSNumber's -initWithBool:YES -> true
+ @li NSNumber's -initWithBool:NO -> false
+ @li NSNumber -> number
+
+ @note 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.
+
+ */
+
+#import "SBJsonParser.h"
+#import "SBJsonWriter.h"
+#import "SBJsonStreamParser.h"
+#import "SBJsonStreamParserAdapter.h"
+#import "SBJsonStreamWriter.h"
+#import "NSObject+SBJson.h"
+
107 SBJson/SBJsonParser.h
@@ -0,0 +1,107 @@
+/*
+ Copyright (C) 2009 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 <Foundation/Foundation.h>
+
+/**
+ @brief Parse JSON Strings and NSData objects
+
+ This uses SBJsonStreamParser internally.
+
+ @see @ref objc2json
+
+ */
+
+@interface SBJsonParser : NSObject {
+
+@private
+ NSString *error;
+ NSUInteger depth, maxDepth;
+
+}
+
+/**
+ @brief The maximum recursing depth.
+
+ Defaults to 32. If the input is nested deeper than this the input will be deemed to be
+ malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can
+ turn off this security feature by setting the maxDepth value to 0.
+ */
+@property NSUInteger maxDepth;
+
+/**
+ @brief Description of parse error
+
+ This method returns the trace of the last method that failed.
+ You need to check the return value of the call you're making to figure out
+ if the call actually failed, before you know call this method.
+
+ @return A string describing the error encountered, or nil if no error occured.
+
+ */
+@property(copy) NSString *error;
+
+/**
+ @brief Return the object represented by the given NSData object.
+
+ The data *must* be UTF8 encoded.
+
+ @param data An NSData containing UTF8 encoded data to parse.
+ @return The NSArray or NSDictionary represented by the object, or nil if an error occured.
+
+ */
+- (id)objectWithData:(NSData*)data;
+
+/**
+ @brief Return the object represented by the given string
+
+ This method converts its input to an NSData object containing UTF8 and calls -objectWithData: with it.
+
+ @return The NSArray or NSDictionary represented by the object, or nil if an error occured.
+ */
+- (id)objectWithString:(NSString *)repr;
+
+/**
+ @brief Return the object represented by the given string
+
+ This method calls objectWithString: internally. If an error occurs, and if @p error
+ is not nil, it creates an NSError object and returns this through its second argument.
+
+ @param jsonText the json string to parse
+ @param error pointer to an NSError object to populate on error
+
+ @return The NSArray or NSDictionary represented by the object, or nil if an error occured.
+ */
+
+- (id)objectWithString:(NSString*)jsonText
+ error:(NSError**)error;
+
+@end
+
+
104 SBJson/SBJsonParser.m
@@ -0,0 +1,104 @@
+/*
+ Copyright (C) 2009,2010 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 "SBJsonParser.h"
+#import "SBJsonStreamParser.h"
+#import "SBJsonStreamParserAdapter.h"
+#import "SBJsonStreamParserAccumulator.h"
+
+@implementation SBJsonParser
+
+@synthesize maxDepth;
+@synthesize error;
+
+- (id)init {
+ self = [super init];
+ if (self)
+ self.maxDepth = 32u;
+ return self;
+}
+
+- (void)dealloc {
+ [error release];
+ [super dealloc];
+}
+
+#pragma mark Methods
+
+- (id)objectWithData:(NSData *)data {
+
+ if (!data) {
+ self.error = @"Input was 'nil'";
+ return nil;
+ }
+
+ SBJsonStreamParserAccumulator *accumulator = [[[SBJsonStreamParserAccumulator alloc] init] autorelease];
+
+ SBJsonStreamParserAdapter *adapter = [[[SBJsonStreamParserAdapter alloc] init] autorelease];
+ adapter.delegate = accumulator;
+
+ SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease];
+ parser.maxDepth = self.maxDepth;
+ parser.delegate = adapter;
+
+ switch ([parser parse:data]) {
+ case SBJsonStreamParserComplete:
+ return accumulator.value;
+ break;
+
+ case SBJsonStreamParserWaitingForData:
+ self.error = @"Unexpected end of input";
+ break;
+
+ case SBJsonStreamParserError:
+ self.error = parser.error;
+ break;
+ }
+
+ return nil;
+}
+
+- (id)objectWithString:(NSString *)repr {
+ return [self objectWithData:[repr dataUsingEncoding:NSUTF8StringEncoding]];
+}
+
+- (id)objectWithString:(NSString*)repr error:(NSError**)error_ {
+ id tmp = [self objectWithString:repr];
+ if (tmp)
+ return tmp;
+
+ if (error_) {
+ NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil];
+ *error_ = [NSError errorWithDomain:@"org.brautaset.SBJsonParser.ErrorDomain" code:0 userInfo:ui];
+ }
+
+ return nil;
+}
+
+@end
167 SBJson/SBJsonStreamParser.h
@@ -0,0 +1,167 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 <Foundation/Foundation.h>
+
+@class SBJsonTokeniser;
+@class SBJsonStreamParser;
+@class SBJsonStreamParserState;
+
+typedef enum {
+ SBJsonStreamParserComplete,
+ SBJsonStreamParserWaitingForData,
+ SBJsonStreamParserError,
+} SBJsonStreamParserStatus;
+
+
+/**
+ @brief Delegate for interacting directly with the stream parser
+
+ You will most likely find it much more convenient to implement the
+ SBJsonStreamParserAdapterDelegate protocol instead.
+ */
+@protocol SBJsonStreamParserDelegate
+
+/// Called when object start is found
+- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser;
+
+/// Called when object key is found
+- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key;
+
+/// Called when object end is found
+- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser;
+
+/// Called when array start is found
+- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser;
+
+/// Called when array end is found
+- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser;
+
+/// Called when a boolean value is found
+- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x;
+
+/// Called when a null value is found
+- (void)parserFoundNull:(SBJsonStreamParser*)parser;
+
+/// Called when a number is found
+- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num;
+
+/// Called when a string is found
+- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string;
+
+@end
+
+
+/**
+ @brief Parse a stream of JSON data.
+
+ Using this class directly you can reduce the apparent latency for each
+ download/parse cycle of documents over a slow connection. You can start
+ parsing *and return chunks of the parsed document* before the entire
+ document is downloaded.
+
+ Using this class is also useful to parse huge documents on disk
+ bit by bit so you don't have to keep them all in memory.
+
+ @see SBJsonStreamParserAdapter for more information.
+
+ @see @ref objc2json
+
+ */
+@interface SBJsonStreamParser : NSObject {
+@private
+ BOOL supportMultipleDocuments;
+ id<SBJsonStreamParserDelegate> delegate;
+ SBJsonTokeniser *tokeniser;
+ NSMutableArray *stateStack;
+ __weak SBJsonStreamParserState *state;
+ NSUInteger maxDepth;
+ NSString *error;
+}
+
+@property (nonatomic, assign) __weak SBJsonStreamParserState *state; // Private
+@property (nonatomic, readonly, retain) NSMutableArray *stateStack; // Private
+
+/**
+ @brief Expect multiple documents separated by whitespace
+
+ Normally the @p -parse: method returns SBJsonStreamParserComplete when it's found a complete JSON document.
+ Attempting to parse any more data at that point is considered an error. ("Garbage after JSON".)
+
+ If you set this property to true the parser will never return SBJsonStreamParserComplete. Rather,
+ once an object is completed it will expect another object to immediately follow, separated
+ only by (optional) whitespace.
+
+ @see The TweetStream app in the Examples
+ */
+@property BOOL supportMultipleDocuments;
+
+/**
+ @brief Delegate to receive messages
+
+ The object set here receives a series of messages as the parser breaks down the JSON stream
+ into valid tokens.
+
+ @note
+ Usually this should be an instance of SBJsonStreamParserAdapter, but you can
+ substitute your own implementation of the SBJsonStreamParserDelegate protocol if you need to.
+ */
+@property (assign) id<SBJsonStreamParserDelegate> delegate;
+
+/**
+ @brief The max parse depth
+
+ If the input is nested deeper than this the parser will halt parsing and return an error.
+
+ Defaults to 32.
+ */
+@property NSUInteger maxDepth;
+
+/// Holds the error after SBJsonStreamParserError was returned
+@property (copy) NSString *error;
+
+/**
+ @brief Parse some JSON
+
+ The JSON is assumed to be UTF8 encoded. This can be a full JSON document, or a part of one.
+
+ @param data An NSData object containing the next chunk of JSON
+
+ @return
+ @li SBJsonStreamParserComplete if a full document was found
+ @li SBJsonStreamParserWaitingForData if a partial document was found and more data is required to complete it
+ @li SBJsonStreamParserError if an error occured. (See the error property for details in this case.)
+
+ */
+- (SBJsonStreamParserStatus)parse:(NSData*)data;
+
+@end
251 SBJson/SBJsonStreamParser.m
@@ -0,0 +1,251 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 "SBJsonStreamParser.h"
+#import "SBJsonTokeniser.h"
+#import "SBJsonStreamParserState.h"
+#import <limits.h>
+
+@implementation SBJsonStreamParser
+
+@synthesize supportMultipleDocuments;
+@synthesize error;
+@synthesize delegate;
+@synthesize maxDepth;
+@synthesize state;
+@synthesize stateStack;
+
+#pragma mark Housekeeping
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ maxDepth = 32u;
+ stateStack = [[NSMutableArray alloc] initWithCapacity:maxDepth];
+ state = [SBJsonStreamParserStateStart sharedInstance];
+ tokeniser = [[SBJsonTokeniser alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ self.error = nil;
+ self.state = nil;
+ [stateStack release];
+ [tokeniser release];
+ [super dealloc];
+}
+
+#pragma mark Methods
+
+- (NSString*)tokenName:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_array_start:
+ return @"start of array";
+ break;
+
+ case sbjson_token_array_end:
+ return @"end of array";
+ break;
+
+ case sbjson_token_number:
+ return @"number";
+ break;
+
+ case sbjson_token_string:
+ return @"string";
+ break;
+
+ case sbjson_token_true:
+ case sbjson_token_false:
+ return @"boolean";
+ break;
+
+ case sbjson_token_null:
+ return @"null";
+ break;
+
+ case sbjson_token_keyval_separator:
+ return @"key-value separator";
+ break;
+
+ case sbjson_token_separator:
+ return @"value separator";
+ break;
+
+ case sbjson_token_object_start:
+ return @"start of object";
+ break;
+
+ case sbjson_token_object_end:
+ return @"end of object";
+ break;
+
+ case sbjson_token_eof:
+ case sbjson_token_error:
+ break;
+ }
+ NSAssert(NO, @"Should not get here");
+ return @"<aaiiie!>";
+}
+
+- (void)maxDepthError {
+ self.error = [NSString stringWithFormat:@"Input depth exceeds max depth of %lu", maxDepth];
+ self.state = [SBJsonStreamParserStateError sharedInstance];
+}
+
+- (void)handleObjectStart {
+ if (stateStack.count >= maxDepth) {
+ [self maxDepthError];
+ return;
+ }
+
+ [delegate parserFoundObjectStart:self];
+ [stateStack addObject:state];
+ self.state = [SBJsonStreamParserStateObjectStart sharedInstance];
+}
+
+- (void)handleArrayStart {
+ if (stateStack.count >= maxDepth) {
+ [self maxDepthError];
+ return;
+ }
+
+ [delegate parserFoundArrayStart:self];
+ [stateStack addObject:state];
+ self.state = [SBJsonStreamParserStateArrayStart sharedInstance];
+}
+
+- (SBJsonStreamParserStatus)parse:(NSData *)data_ {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ @try {
+ [tokeniser appendData:data_];
+
+ for (;;) {
+
+ if ([state isKindOfClass:[SBJsonStreamParserStateError class]])
+ return SBJsonStreamParserError;
+
+ NSObject *token;
+ sbjson_token_t tok = [tokeniser getToken:&token];
+ switch (tok) {
+ case sbjson_token_eof:
+ return [state parserShouldReturn:self];
+ break;
+
+ case sbjson_token_error:
+ self.state = [SBJsonStreamParserStateError sharedInstance];
+ self.error = tokeniser.error;
+ return SBJsonStreamParserError;
+ break;
+
+ default:
+
+ if (![state parser:self shouldAcceptToken:tok]) {
+ NSString *tokenName = [self tokenName:tok];
+ NSString *stateName = [state name];
+
+ self.error = [NSString stringWithFormat:@"Token '%@' not expected %@", tokenName, stateName];
+ self.state = [SBJsonStreamParserStateError sharedInstance];
+ return SBJsonStreamParserError;
+ }
+
+ switch (tok) {
+ case sbjson_token_object_start:
+ [self handleObjectStart];
+ break;
+
+ case sbjson_token_object_end:
+ self.state = [stateStack lastObject];
+ [stateStack removeLastObject];
+ [state parser:self shouldTransitionTo:tok];
+ [delegate parserFoundObjectEnd:self];
+ break;
+
+ case sbjson_token_array_start:
+ [self handleArrayStart];
+ break;
+
+ case sbjson_token_array_end:
+ self.state = [stateStack lastObject];
+ [stateStack removeLastObject];
+ [state parser:self shouldTransitionTo:tok];
+ [delegate parserFoundArrayEnd:self];
+ break;
+
+ case sbjson_token_separator:
+ case sbjson_token_keyval_separator:
+ [state parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_true:
+ [delegate parser:self foundBoolean:YES];
+ [state parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_false:
+ [delegate parser:self foundBoolean:NO];
+ [state parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_null:
+ [delegate parserFoundNull:self];
+ [state parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_number:
+ [delegate parser:self foundNumber:(NSNumber*)token];
+ [state parser:self shouldTransitionTo:tok];
+ break;
+
+ case sbjson_token_string:
+ if ([state needKey])
+ [delegate parser:self foundObjectKey:(NSString*)token];
+ else
+ [delegate parser:self foundString:(NSString*)token];
+ [state parser:self shouldTransitionTo:tok];
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+ return SBJsonStreamParserComplete;
+ }
+ @finally {
+ [pool drain];
+ }
+}
+
+@end
40 SBJson/SBJsonStreamParserAccumulator.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2011 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 <Foundation/Foundation.h>
+#import "SBJsonStreamParserAdapter.h"
+
+@interface SBJsonStreamParserAccumulator : NSObject <SBJsonStreamParserAdapterDelegate> {
+@private
+ id value;
+}
+
+@property (readonly, copy) id value;
+
+@end
51 SBJson/SBJsonStreamParserAccumulator.m
@@ -0,0 +1,51 @@
+/*
+ Copyright (C) 2011 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 "SBJsonStreamParserAccumulator.h"
+
+@implementation SBJsonStreamParserAccumulator
+
+@synthesize value;
+
+- (void)dealloc {
+ [value release];
+ [super dealloc];
+}
+
+#pragma mark SBJsonStreamParserAdapterDelegate
+
+- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray *)array {
+ value = [array retain];
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary *)dict {
+ value = [dict retain];
+}
+
+@end
148 SBJson/SBJsonStreamParserAdapter.h
@@ -0,0 +1,148 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 <Foundation/Foundation.h>
+#import "SBJsonStreamParser.h"
+
+typedef enum {
+ SBJsonStreamParserAdapterNone,
+ SBJsonStreamParserAdapterArray,
+ SBJsonStreamParserAdapterObject,
+} SBJsonStreamParserAdapterType;
+
+/**
+ @brief Delegate for getting objects & arrays from the stream parser adapter
+
+ @see The TweetStream example project.
+ */
+@protocol SBJsonStreamParserAdapterDelegate
+
+/**
+ @brief Called if a JSON array is found
+
+ This method is called if a JSON array is found.
+
+ */
+- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray*)array;
+
+/**
+ @brief Called when a JSON object is found
+
+ This method is called if a JSON object is found.
+ */
+- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary*)dict;
+
+@end
+
+/**
+ @brief SBJsonStreamParserDelegate protocol adapter
+
+ Rather than implementing the SBJsonStreamParserDelegate protocol yourself you will
+ most likely find it much more convenient to use an instance of this class and
+ implement the SBJsonStreamParserAdapterDelegate protocol instead.
+
+ Normally you would only get one call from either the -parser:foundArray: or
+ -parser:foundObject: method. However, if your inputs contains multiple JSON
+ documents and you set the parser's -supportMultipleDocuments property to YES
+ you will get one call for each full method.
+
+ @code
+ SBJsonStreamParserAdapter *adapter = [[[SBJsonStreamParserAdapter alloc] init] autorelease];
+ adapter.delegate = self;
+
+ SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease];
+ parser.delegate = adapter;
+ parser.supportMultipleDocuments = YES;
+
+ // Note that this input contains multiple top-level JSON documents
+ NSData *json = [@"[]{}[]{}" dataWithEncoding:NSUTF8StringEncoding];
+ [parser parse:data];
+ @endcode
+
+ In the above example @p self will have the following sequence of methods called on it:
+
+ @li -parser:foundArray:
+ @li -parser:foundObject:
+ @li -parser:foundArray:
+ @li -parser:foundObject:
+
+ Often you won't have control over the input you're parsing, so can't make use of
+ this feature. But, all is not lost: this class will let you get the same effect by
+ allowing you to skip one or more of the outer enclosing objects. Thus, the next
+ example results in the same sequence of -parser:foundArray: / -parser:foundObject:
+ being called on your delegate.
+
+ @code
+ SBJsonStreamParserAdapter *adapter = [[[SBJsonStreamParserAdapter alloc] init] autorelease];
+ adapter.delegate = self;
+ adapter.levelsToSkip = 1;
+
+ SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease];
+ parser.delegate = adapter;
+
+ // Note that this input contains A SINGLE top-level document
+ NSData *json = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
+ [parser parse:data];
+ @endcode
+
+*/
+@interface SBJsonStreamParserAdapter : NSObject <SBJsonStreamParserDelegate> {
+@private
+ id<SBJsonStreamParserAdapterDelegate> delegate;
+ NSUInteger levelsToSkip, depth;
+ __weak NSMutableArray *array;
+ __weak NSMutableDictionary *dict;
+ NSMutableArray *keyStack;
+ NSMutableArray *stack;
+
+ SBJsonStreamParserAdapterType currentType;
+}
+
+/**
+ @brief How many levels to skip
+
+ This is useful for parsing huge JSON documents, or documents coming in over a very slow link.
+
+ If you set this to N it will skip the outer N levels and call the -parser:foundArray:
+ or -parser:foundObject: methods for each of the inner objects, as appropriate.
+
+ @see The StreamParserIntegrationTest.m file for examples
+*/
+@property NSUInteger levelsToSkip;
+
+/**
+ @brief Your delegate object
+ Set this to the object you want to receive the SBJsonStreamParserAdapterDelegate messages.
+ */
+@property (assign) id<SBJsonStreamParserAdapterDelegate> delegate;
+
+@end
171 SBJson/SBJsonStreamParserAdapter.m
@@ -0,0 +1,171 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 "SBJsonStreamParserAdapter.h"
+
+@interface SBJsonStreamParserAdapter ()
+
+- (void)pop;
+- (void)parser:(SBJsonStreamParser*)parser found:(id)obj;
+
+@end
+
+
+
+@implementation SBJsonStreamParserAdapter
+
+@synthesize delegate;
+@synthesize levelsToSkip;
+
+#pragma mark Housekeeping
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ keyStack = [[NSMutableArray alloc] initWithCapacity:32];
+ stack = [[NSMutableArray alloc] initWithCapacity:32];
+
+ currentType = SBJsonStreamParserAdapterNone;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [keyStack release];
+ [stack release];
+ [super dealloc];
+}
+
+#pragma mark Private methods
+
+- (void)pop {
+ [stack removeLastObject];
+ array = nil;
+ dict = nil;
+ currentType = SBJsonStreamParserAdapterNone;
+
+ id value = [stack lastObject];
+
+ if ([value isKindOfClass:[NSArray class]]) {
+ array = value;
+ currentType = SBJsonStreamParserAdapterArray;
+ } else if ([value isKindOfClass:[NSDictionary class]]) {
+ dict = value;
+ currentType = SBJsonStreamParserAdapterObject;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser found:(id)obj {
+ NSParameterAssert(obj);
+
+ switch (currentType) {
+ case SBJsonStreamParserAdapterArray:
+ [array addObject:obj];
+ break;
+
+ case SBJsonStreamParserAdapterObject:
+ NSParameterAssert(keyStack.count);
+ [dict setObject:obj forKey:[keyStack lastObject]];
+ [keyStack removeLastObject];
+ break;
+
+ case SBJsonStreamParserAdapterNone:
+ if ([obj isKindOfClass:[NSArray class]]) {
+ [delegate parser:parser foundArray:obj];
+ } else {
+ [delegate parser:parser foundObject:obj];
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+#pragma mark Delegate methods
+
+- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser {
+ if (++depth > levelsToSkip) {
+ dict = [[NSMutableDictionary new] autorelease];
+ [stack addObject:dict];
+ currentType = SBJsonStreamParserAdapterObject;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key_ {
+ [keyStack addObject:key_];
+}
+
+- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser {
+ if (depth-- > levelsToSkip) {
+ id value = [dict retain];
+ [self pop];
+ [self parser:parser found:value];
+ [value release];
+ }
+}
+
+- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser {
+ if (++depth > levelsToSkip) {
+ array = [[NSMutableArray new] autorelease];
+ [stack addObject:array];
+ currentType = SBJsonStreamParserAdapterArray;
+ }
+}
+
+- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser {
+ if (depth-- > levelsToSkip) {
+ id value = [array retain];
+ [self pop];
+ [self parser:parser found:value];
+ [value release];
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x {
+ [self parser:parser found:[NSNumber numberWithBool:x]];
+}
+
+- (void)parserFoundNull:(SBJsonStreamParser*)parser {
+ [self parser:parser found:[NSNull null]];
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num {
+ [self parser:parser found:num];
+}
+
+- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string {
+ [self parser:parser found:string];
+}
+
+@end
81 SBJson/SBJsonStreamParserState.h
@@ -0,0 +1,81 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 <Foundation/Foundation.h>
+
+#import "SBJsonTokeniser.h"
+#import "SBJsonStreamParser.h"
+
+@interface SBJsonStreamParserState : NSObject
++ (id)sharedInstance;
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token;
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser;
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok;
+- (BOOL)needKey;
+
+- (NSString*)name;
+
+@end
+
+@interface SBJsonStreamParserStateStart : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateComplete : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateError : SBJsonStreamParserState
+@end
+
+
+@interface SBJsonStreamParserStateObjectStart : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectGotKey : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectSeparator : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectGotValue : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateObjectNeedKey : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateArrayStart : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateArrayGotValue : SBJsonStreamParserState
+@end
+
+@interface SBJsonStreamParserStateArrayNeedValue : SBJsonStreamParserState
+@end
347 SBJson/SBJsonStreamParserState.m
@@ -0,0 +1,347 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 "SBJsonStreamParserState.h"
+#import "SBJsonStreamParser.h"
+
+#define SINGLETON \
++ (id)sharedInstance { \
+ static id state; \
+ if (!state) state = [[self alloc] init]; \
+ return state; \
+}
+
+@implementation SBJsonStreamParserState
+
++ (id)sharedInstance { return nil; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return NO;
+}
+
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser {
+ return SBJsonStreamParserWaitingForData;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {}
+
+- (BOOL)needKey {
+ return NO;
+}
+
+- (NSString*)name {
+ return @"<aaiie!>";
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateStart
+
+SINGLETON
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return token == sbjson_token_array_start || token == sbjson_token_object_start;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+
+ SBJsonStreamParserState *state = nil;
+ switch (tok) {
+ case sbjson_token_array_start:
+ state = [SBJsonStreamParserStateArrayStart sharedInstance];
+ break;
+
+ case sbjson_token_object_start:
+ state = [SBJsonStreamParserStateObjectStart sharedInstance];
+ break;
+
+ case sbjson_token_array_end:
+ case sbjson_token_object_end:
+ if (parser.supportMultipleDocuments)
+ state = parser.state;
+ else
+ state = [SBJsonStreamParserStateComplete sharedInstance];
+ break;
+
+ case sbjson_token_eof:
+ return;
+
+ default:
+ state = [SBJsonStreamParserStateError sharedInstance];
+ break;
+ }
+
+
+ parser.state = state;
+}
+
+- (NSString*)name { return @"before outer-most array or object"; }
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateComplete
+
+SINGLETON
+
+- (NSString*)name { return @"after outer-most array or object"; }
+
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser {
+ return SBJsonStreamParserComplete;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateError
+
+SINGLETON
+
+- (NSString*)name { return @"in error"; }
+
+- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser {
+ return SBJsonStreamParserError;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectStart
+
+SINGLETON
+
+- (NSString*)name { return @"at beginning of object"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_end:
+ case sbjson_token_string:
+ return YES;
+ break;
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateObjectGotKey sharedInstance];
+}
+
+- (BOOL)needKey {
+ return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectGotKey
+
+SINGLETON
+
+- (NSString*)name { return @"after object key"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return token == sbjson_token_keyval_separator;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateObjectSeparator sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectSeparator
+
+SINGLETON
+
+- (NSString*)name { return @"as object value"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_start:
+ case sbjson_token_array_start:
+ case sbjson_token_true:
+ case sbjson_token_false:
+ case sbjson_token_null:
+ case sbjson_token_number:
+ case sbjson_token_string:
+ return YES;
+ break;
+
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateObjectGotValue sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectGotValue
+
+SINGLETON
+
+- (NSString*)name { return @"after object value"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_end:
+ case sbjson_token_separator:
+ return YES;
+ break;
+ default:
+ return NO;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateObjectNeedKey sharedInstance];
+}
+
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateObjectNeedKey
+
+SINGLETON
+
+- (NSString*)name { return @"in place of object key"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return sbjson_token_string == token;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateObjectGotKey sharedInstance];
+}
+
+- (BOOL)needKey {
+ return YES;
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateArrayStart
+
+SINGLETON
+
+- (NSString*)name { return @"at array start"; }
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_object_end:
+ case sbjson_token_keyval_separator:
+ case sbjson_token_separator:
+ return NO;
+ break;
+
+ default:
+ return YES;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateArrayGotValue sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateArrayGotValue
+
+SINGLETON
+
+- (NSString*)name { return @"after array value"; }
+
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ return token == sbjson_token_array_end || token == sbjson_token_separator;
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ if (tok == sbjson_token_separator)
+ parser.state = [SBJsonStreamParserStateArrayNeedValue sharedInstance];
+}
+
+@end
+
+#pragma mark -
+
+@implementation SBJsonStreamParserStateArrayNeedValue
+
+SINGLETON
+
+- (NSString*)name { return @"as array value"; }
+
+
+- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token {
+ switch (token) {
+ case sbjson_token_array_end:
+ case sbjson_token_keyval_separator:
+ case sbjson_token_object_end:
+ case sbjson_token_separator:
+ return NO;
+ break;
+
+ default:
+ return YES;
+ break;
+ }
+}
+
+- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {
+ parser.state = [SBJsonStreamParserStateArrayGotValue sharedInstance];
+}
+
+@end
+
194 SBJson/SBJsonStreamWriter.h
@@ -0,0 +1,194 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 <Foundation/Foundation.h>
+
+/// Enable JSON writing for non-native objects
+@interface NSObject (SBProxyForJson)
+
+/**
+ @brief Allows generation of JSON for otherwise unsupported classes.
+
+ If you have a custom class that you want to create a JSON representation
+ for you can implement this method in your class. It should return a
+ representation of your object defined in terms of objects that can be
+ translated into JSON. For example, a Person object might implement it like this:
+
+ @code
+ - (id)proxyForJson {
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ name, @"name",
+ phone, @"phone",
+ email, @"email",
+ nil];
+ }
+ @endcode
+
+ */
+- (id)proxyForJson;
+
+@end
+
+@class SBJsonStreamWriter;
+
+@protocol SBJsonStreamWriterDelegate
+
+- (void)writer:(SBJsonStreamWriter*)writer appendBytes:(const void *)bytes length:(NSUInteger)length;
+
+@end
+
+@class SBJsonStreamWriterState;
+
+/**
+ @brief The Stream Writer class.
+
+ Accepts a stream of messages and writes JSON of these to its delegate object.
+
+ This class provides a range of high-, mid- and low-level methods. You can mix
+ and match calls to these. For example, you may want to call -writeArrayOpen
+ to start an array and then repeatedly call -writeObject: with various objects
+ before finishing off with a -writeArrayClose call.
+
+ @see @ref json2objc
+
+ */
+
+@interface SBJsonStreamWriter : NSObject {
+@private
+ NSString *error;
+ NSMutableArray *stateStack;
+ __weak SBJsonStreamWriterState *state;
+ id<SBJsonStreamWriterDelegate> delegate;
+ NSUInteger maxDepth;
+ BOOL sortKeys, humanReadable;
+}
+
+@property (nonatomic, assign) __weak SBJsonStreamWriterState *state; // Internal
+@property (nonatomic, readonly, retain) NSMutableArray *stateStack; // Internal
+
+/**
+ @brief delegate to receive JSON output
+ Delegate that will receive messages with output.
+ */
+@property (assign) id<SBJsonStreamWriterDelegate> delegate;
+
+/**
+ @brief The maximum recursing depth.
+
+ Defaults to 512. If the input is nested deeper than this the input will be deemed to be
+ malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can
+ turn off this security feature by setting the maxDepth value to 0.
+ */
+@property NSUInteger maxDepth;
+
+/**
+ @brief 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 between tokens. 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;
+
+/**
+ @brief Whether or not to sort the dictionary keys in the output.
+
+ If this is set to YES, the dictionary keys in the JSON output will be in sorted order.
+ (This is useful if you need to compare two structures, for example.) The default is NO.
+ */
+@property BOOL sortKeys;
+
+/// Contains the error description after an error has occured.
+@property (copy) NSString *error;
+
+/**
+ Write an NSDictionary to the JSON stream.
+ @return YES if successful, or NO on failure
+ */
+- (BOOL)writeObject:(NSDictionary*)dict;
+
+/**
+ Write an NSArray to the JSON stream.
+ @return YES if successful, or NO on failure
+ */
+- (BOOL)writeArray:(NSArray *)array;
+
+/**
+ Start writing an Object to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeObjectOpen;
+
+/**
+ Close the current object being written
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeObjectClose;
+
+/** Start writing an Array to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeArrayOpen;
+
+/** Close the current Array being written
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeArrayClose;
+
+/** Write a null to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeNull;
+
+/** Write a boolean to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeBool:(BOOL)x;
+
+/** Write a Number to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeNumber:(NSNumber*)n;
+
+/** Write a String to the stream
+ @return YES if successful, or NO on failure
+*/
+- (BOOL)writeString:(NSString*)s;
+
+@end
+
+@interface SBJsonStreamWriter (Private)
+- (BOOL)writeValue:(id)v;
+- (void)appendBytes:(const void *)bytes length:(NSUInteger)length;
+@end
+
375 SBJson/SBJsonStreamWriter.m
@@ -0,0 +1,375 @@
+/*
+ Copyright (c) 2010, 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 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
+ HOLDER 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 "SBJsonStreamWriter.h"
+#import "SBJsonStreamWriterState.h"
+
+static NSDecimalNumber *kNotANumber;
+static id kStaticStringCache;
+
+
+@implementation SBJsonStreamWriter
+
+@synthesize error;
+@synthesize maxDepth;
+@synthesize state;
+@synthesize stateStack;
+@synthesize humanReadable;
+@synthesize sortKeys;
+
++ (void)initialize {
+ kNotANumber = [NSDecimalNumber notANumber];
+
+ Class cacheClass = NSClassFromString(@"NSCache");
+ if (cacheClass) {
+ NSLog(@"%s NSCache supported", __FUNCTION__);
+ kStaticStringCache = [[cacheClass alloc] init];
+ }else {
+ NSLog(@"%s NSCache not supported", __FUNCTION__);
+ }
+
+
+}
+
+#pragma mark Housekeeping
+
+@synthesize delegate;
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ maxDepth = 32u;
+ stateStack = [[NSMutableArray alloc] initWithCapacity:maxDepth];
+ state = [SBJsonStreamWriterStateStart sharedInstance];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ self.error = nil;
+ self.state = nil;
+ [stateStack release];
+ [super dealloc];
+}
+
+#pragma mark Methods
+
+- (void)appendBytes:(const void *)bytes length:(NSUInteger)length {
+ [delegate writer:self appendBytes:bytes length:length];
+}
+
+- (BOOL)writeObject:(NSDictionary *)dict {
+ if (![self writeObjectOpen])
+ return NO;
+
+ NSArray *keys = [dict allKeys];
+ if (sortKeys)
+ keys = [keys sortedArrayUsingSelector:@selector(compare:)];
+
+ for (id k in keys) {
+ if (![k isKindOfClass:[NSString class]]) {
+ self.error = [NSString stringWithFormat:@"JSON object key must be string: %@", k];
+ return NO;
+ }
+
+ if (![self writeString:k])
+ return NO;
+ if (![self writeValue:[dict objectForKey:k]])
+ return NO;
+ }
+
+ return [self writeObjectClose];
+}
+
+- (BOOL)writeArray:(NSArray*)array {
+ if (![self writeArrayOpen])
+ return NO;
+ for (id v in array)
+ if (![self writeValue:v])
+ return NO;
+ return [self writeArrayClose];
+}
+
+
+- (BOOL)writeObjectOpen {
+ if ([state isInvalidState:self]) return NO;
+ if ([state expectingKey:self]) return NO;
+ [state appendSeparator:self];
+ if (humanReadable && stateStack.count) [state appendWhitespace:self];
+
+ [stateStack addObject:state];
+ self.state = [SBJsonStreamWriterStateObjectStart sharedInstance];
+
+ if (maxDepth && stateStack.count > maxDepth) {
+ self.error = @"Nested too deep";
+ return NO;
+ }
+
+ [delegate writer:self appendBytes:"{" length:1];
+ return YES;
+}
+
+- (BOOL)writeObjectClose {
+ if ([state isInvalidState:self]) return NO;
+
+ SBJsonStreamWriterState *prev = state;
+
+ self.state = [stateStack lastObject];
+ [stateStack removeLastObject];
+
+ if (humanReadable) [prev appendWhitespace:self];
+ [delegate writer:self appendBytes:"}" length:1];
+
+ [state transitionState:self];
+ return YES;
+}
+
+- (BOOL)writeArrayOpen {
+ if ([state isInvalidState:self]) return NO;
+ if ([state expectingKey:self]) return NO;
+ [state appendSeparator:self];
+ if (humanReadable && stateStack.count) [state appendWhitespace:self];
+
+ [stateStack addObject:state];
+ self.state = [SBJsonStreamWriterStateArrayStart sharedInstance];
+
+ if (maxDepth && stateStack.count > maxDepth) {
+ self.error = @"Nested too deep";
+ return NO;
+ }
+
+ [delegate writer:self appendBytes:"[" length:1];
+ return YES;
+}
+
+- (BOOL)writeArrayClose {
+ if ([state isInvalidState:self]) return NO;
+ if ([state expectingKey:self]) return NO;
+
+ SBJsonStreamWriterState *prev = state;
+
+ self.state = [stateStack lastObject];
+ [stateStack removeLastObject];
+
+ if (humanReadable) [prev appendWhitespace:self];
+ [delegate writer:self appendBytes:"]" length:1];
+
+ [state transitionState:self];
+ return YES;
+}
+
+- (BOOL)writeNull {
+ if ([state isInvalidState:self]) return NO;
+ if ([state expectingKey:self]) return NO;
+ [state appendSeparator:self];
+ if (humanReadable) [state appendWhitespace:self];
+
+ [delegate writer:self appendBytes:"null" length:4];
+ [state transitionState:self];
+ return YES;
+}
+
+- (BOOL)writeBool:(BOOL)x {
+ if ([state isInvalidState:self]) return NO;
+ if ([state expectingKey:self]) return NO;
+ [state appendSeparator:self];
+ if (humanReadable) [state appendWhitespace:self];
+
+ if (x)
+ [delegate writer:self appendBytes:"true" length:4];
+ else
+ [delegate writer:self appendBytes:"false" length:5];
+ [state transitionState:self];
+ return YES;
+}
+
+
+- (BOOL)writeValue:(id)o {
+ if ([o isKindOfClass:[NSDictionary class]]) {
+ return [self writeObject:o];
+
+ } else if ([o isKindOfClass:[NSArray class]]) {
+ return [self writeArray:o];
+
+ } else if ([o isKindOfClass:[NSString class]]) {
+ [self writeString:o];
+ return YES;
+
+ } else if ([o isKindOfClass:[NSNumber class]]) {
+ return [self writeNumber:o];
+
+ } else if ([o isKindOfClass:[NSNull class]]) {
+ return [self writeNull];
+
+ } else if ([o respondsToSelector:@selector(proxyForJson)]) {
+ return [self writeValue:[o proxyForJson]];
+
+ }
+
+ self.error = [NSString stringWithFormat:@"JSON serialisation not supported for %@", [o class]];
+ return NO;
+}
+
+static const char *strForChar(int c) {
+ switch (c) {
+ case 0: return "\\u0000"; break;
+ case 1: return "\\u0001"; break;
+ case 2: return "\\u0002"; break;
+ case 3: return "\\u0003"; break;
+ case 4: return "\\u0004"; break;
+ case 5: return "\\u0005"; break;
+ case 6: return "\\u0006"; break;
+ case 7: return "\\u0007"; break;
+ case 8: return "\\b"; break;
+ case 9: return "\\t"; break;
+ case 10: return "\\n"; break;
+ case 11: return "\\u000b"; break;
+ case 12: return "\\f"; break;
+ case 13: return "\\r"; break;
+ case 14: return "\\u000e"; break;
+ case 15: return "\\u000f"; break;
+ case 16: return "\\u0010"; break;
+ case 17: return "\\u0011"; break;
+ case 18: return "\\u0012"; break;
+ case 19: return "\\u0013"; break;
+ case 20: return "\\u0014"; break;
+ case 21: return "\\u0015"; break;
+ case 22: return "\\u0016"; break;
+ case 23: return "\\u0017"; break;
+ case 24: return "\\u0018"; break;
+ case 25: return "\\u0019"; break;
+ case 26: return "\\u001a"; break;
+ case 27: return "\\u001b"; break;
+ case 28: return "\\u001c"; break;
+ case 29: return "\\u001d"; break;
+ case 30: return "\\u001e"; break;
+ case 31: return "\\u001f"; break;
+ case 34: return "\\\""; break;
+ case 92: return "\\\\"; break;
+ }
+ NSLog(@"FUTFUTFUT: -->'%c'<---", c);
+ return "FUTFUTFUT";
+}
+
+- (BOOL)writeString:(NSString*)string {
+ if ([state isInvalidState:self]) return NO;
+ [state appendSeparator:self];
+ if (humanReadable) [state appendWhitespace:self];
+
+ NSMutableData *buf = [kStaticStringCache objectForKey:string];
+ if (!buf) {
+
+ NSUInteger len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ const char *utf8 = [string UTF8String];
+ NSUInteger written = 0, i = 0;
+
+ buf = [NSMutableData dataWithCapacity:(NSUInteger)(len * 1.1f)];
+ [buf appendBytes:"\"" length:1];
+
+ for (i = 0; i < len; i++) {
+ int c = utf8[i];
+ BOOL isControlChar = c >= 0 && c < 32;
+ if (isControlChar || c == '"' || c == '\\') {
+ if (i - written)
+ [buf appendBytes:utf8 + written length:i - written];
+ written = i + 1;
+
+ const char *t = strForChar(c);
+ [buf appendBytes:t length:strlen(t)];
+ }
+ }
+
+ if (i - written)
+ [buf appendBytes:utf8 + written length:i - written];
+
+ [buf appendBytes:"\"" length:1];
+ [kStaticStringCache setObject:buf forKey:string];
+ }
+
+ [delegate writer:self appendBytes:[buf bytes] length:[buf length]];
+ [state transitionState:self];
+ return YES;
+}
+
+- (BOOL)writeNumber:(NSNumber*)number {
+ if ((CFBooleanRef)number == kCFBooleanTrue || (CFBooleanRef)number == kCFBooleanFalse)
+ return [self writeBool:[number boolValue]];
+
+ if ([state isInvalidState:self]) return NO;
+ if ([state expectingKey:self]) return NO;
+ [state appendSeparator:self];
+ if (humanReadable) [state appendWhitespace:self];
+
+ if ((CFNumberRef)number == kCFNumberPositiveInfinity) {
+ self.error = @"+Infinity is not a valid number in JSON";
+ return NO;
+
+ } else if ((CFNumberRef)number == kCFNumberNegativeInfinity) {
+ self.error = @"-Infinity is not a valid number in JSON";
+ return NO;
+
+ } else if ((CFNumberRef)number == kCFNumberNaN) {
+ self.error = @"NaN is not a valid number in JSON";
+ return NO;
+
+ } else if (number == kNotANumber) {
+ self.error = @"NaN is not a valid number in JSON";
+ return NO;
+ }
+
+ const char *objcType = [number objCType];
+ char num[128];
+ size_t len;
+
+ switch (objcType[0]) {
+ case 'c': case 'i': case 's': case 'l': case 'q':
+ len = snprintf(num, sizeof num, "%lld", [number longLongValue]);
+ break;
+ case 'C': case 'I': case 'S': case 'L': case 'Q':
+ len = snprintf(num, sizeof num, "%llu", [number unsignedLongLongValue]);
+ break;
+ case 'f': case 'd': default:
+ if ([number isKindOfClass:[NSDecimalNumber class]]) {
+ char const *utf8 = [[number stringValue] UTF8String];
+ [delegate writer:self appendBytes:utf8 length: strlen(utf8)];
+ [state transitionState:self];
+ return YES;
+ }
+ len = snprintf(num, sizeof num, "%.17g", [number doubleValue]);
+ break;
+ }
+ [delegate writer:self appendBytes:num length: len];
+ [state transitionState:self];
+ return YES;
+}
+
+@end
39 SBJson/SBJsonStreamWriterAccumulator.h
@@ -0,0 +1,39 @@
+/*
+ Copyright (C) 2011 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 "SBJsonStreamWriter.h"
+
+@interface SBJsonStreamWriterAccumulator : NSObject <SBJsonStreamWriterDelegate> {
+@private
+ NSMutableData *data;
+}
+
+@property (readonly, copy) NSData* data;
+
+@end
56 SBJson/SBJsonStreamWriterAccumulator.m
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2011 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 "SBJsonStreamWriterAccumulator.h"
+
+
+@implementation SBJsonStreamWriterAccumulator
+
+@synthesize data;
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ data = [[NSMutableData alloc] initWithCapacity:8096u];
+ }
+ return self;
+}
+
+- (void)dealloc {