diff --git a/Classes/SBJson.h b/Classes/SBJson.h index 6f109d3b..b25da4a3 100644 --- a/Classes/SBJson.h +++ b/Classes/SBJson.h @@ -78,6 +78,7 @@ #import "SBJsonParser.h" #import "SBJsonWriter.h" #import "SBJsonStreamParser.h" +#import "SBJsonStreamParserAdapter.h" #import "SBJsonStreamWriter.h" #import "NSObject+SBJson.h" diff --git a/Classes/SBJsonParser.m b/Classes/SBJsonParser.m index c7973694..6f2e26b8 100644 --- a/Classes/SBJsonParser.m +++ b/Classes/SBJsonParser.m @@ -29,6 +29,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "SBJsonParser.h" #import "SBJsonStreamParser.h" +#import "SBJsonStreamParserAdapter.h" #import "SBJsonStreamParserAccumulator.h" @implementation SBJsonParser @@ -59,9 +60,12 @@ - (id)objectWithData:(NSData *)data { 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 = accumulator; + parser.delegate = adapter; switch ([parser parse:data]) { case SBJsonStreamParserComplete: diff --git a/Classes/SBJsonStreamParser.h b/Classes/SBJsonStreamParser.h index 2e609638..bced473b 100644 --- a/Classes/SBJsonStreamParser.h +++ b/Classes/SBJsonStreamParser.h @@ -44,34 +44,42 @@ typedef enum { /** - @brief SBJsonStreamParserDelegate protocol adapter - - @see SBJsonStreamParser + @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 -/** - @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; +/// Called when object start is found +- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser; -/** - @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; +/// 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 -typedef enum { - SBJsonStreamParserNone, - SBJsonStreamParserArray, - SBJsonStreamParserObject, -} SBJsonStreamParserType; /** @brief Parse a stream of JSON data. @@ -79,81 +87,24 @@ typedef enum { 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. - - The default behaviour is that the delegate only receives one call from - either the -parser:foundArray: or -parser:foundObject: method when the - document is fully parsed. 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 - SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease]; - parser.delegate = self; - parser.supportMultipleDocuments = YES; - - // Note that this input contains multiple top-level JSON documents - NSData *json = [@"[]{}[]{}" dataWithEncoding:NSUTF8StringEncoding]; - [parser parse:data]; - @endcode + document is downloaded. - In the above example @p self will have the following sequence of methods called on it: + 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. - @li -parser:foundArray: - @li -parser:foundObject: - @li -parser:foundArray: - @li -parser:foundObject: + @see SBJsonStreamParserAdapter for more information. - 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 - SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease]; - parser.delegate = self; - parser.levelsToSkip = 1; - - // Note that this input contains A SINGLE top-level document - NSData *json = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding]; - [parser parse:data]; - @endcode - - @see SBJsonStreamParserDelegate @see @ref objc2json */ @interface SBJsonStreamParser : NSObject { @private SBJsonTokeniser *tokeniser; - - NSUInteger depth; - NSMutableArray *array; - NSMutableDictionary *dict; - NSMutableArray *keyStack; - NSMutableArray *stack; - - SBJsonStreamParserType currentType; } @property (nonatomic, assign) SBJsonStreamParserState *state; // Private @property (nonatomic, readonly, retain) NSMutableArray *stateStack; // Private - -/** - @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 Expect multiple documents separated by whitespace @@ -175,7 +126,7 @@ typedef enum { into valid tokens. @note - Usually this should be an instance of SBJsonStreamParser, but you can + 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 delegate; diff --git a/Classes/SBJsonStreamParser.m b/Classes/SBJsonStreamParser.m index e10ad842..4c578bd8 100644 --- a/Classes/SBJsonStreamParser.m +++ b/Classes/SBJsonStreamParser.m @@ -35,20 +35,8 @@ #import "SBJsonStreamParserState.h" #import -static NSNumber *kTrue; -static NSNumber *kFalse; -static NSNull *kNull; - -@interface SBJsonStreamParser () - -- (void)pop; -- (void)parserFoundObject:(id)obj; - -@end - @implementation SBJsonStreamParser -@synthesize levelsToSkip; @synthesize supportMultipleDocuments; @synthesize error; @synthesize delegate; @@ -58,12 +46,6 @@ @implementation SBJsonStreamParser #pragma mark Housekeeping -+ (void)initialize { - kTrue = [[NSNumber alloc] initWithBool:YES]; - kFalse = [[NSNumber alloc] initWithBool:NO]; - kNull = [NSNull null]; -} - - (id)init { self = [super init]; if (self) { @@ -71,19 +53,13 @@ - (id)init { stateStack = [[NSMutableArray alloc] initWithCapacity:maxDepth]; state = [SBJsonStreamParserStateStart sharedInstance]; tokeniser = [[SBJsonTokeniser alloc] init]; - - keyStack = [[NSMutableArray alloc] initWithCapacity:32]; - stack = [[NSMutableArray alloc] initWithCapacity:32]; - currentType = SBJsonStreamParserNone; - } return self; } - (void)dealloc { - [keyStack release]; - [stack release]; - [error release]; + self.error = nil; + self.state = nil; [stateStack release]; [tokeniser release]; [super dealloc]; @@ -147,112 +123,40 @@ - (void)maxDepthError { self.state = [SBJsonStreamParserStateError sharedInstance]; } - -- (void)pop { - [stack removeLastObject]; - array = nil; - dict = nil; - currentType = SBJsonStreamParserNone; - - id value = [stack lastObject]; - - if ([value isKindOfClass:[NSArray class]]) { - array = value; - currentType = SBJsonStreamParserArray; - } else if ([value isKindOfClass:[NSDictionary class]]) { - dict = value; - currentType = SBJsonStreamParserObject; - } -} - -- (void)parserFoundObject:(id)obj { - NSParameterAssert(obj); - - switch (currentType) { - case SBJsonStreamParserArray: - [array addObject:obj]; - break; - - case SBJsonStreamParserObject: - NSParameterAssert(keyStack.count); - [dict setObject:obj forKey:[keyStack lastObject]]; - [keyStack removeLastObject]; - break; - - case SBJsonStreamParserNone: - if ([obj isKindOfClass:[NSArray class]]) { - [delegate parser:self foundArray:obj]; - } else { - [delegate parser:self foundObject:obj]; - } - break; - - default: - break; - } -} - - (void)handleObjectStart { - if (depth >= maxDepth) { + if (stateStack.count >= maxDepth) { [self maxDepthError]; return; } - + + [delegate parserFoundObjectStart:self]; [stateStack addObject:state]; self.state = [SBJsonStreamParserStateObjectStart sharedInstance]; - - if (++depth > levelsToSkip) { - dict = [[NSMutableDictionary alloc] init]; - [stack addObject:dict]; - [dict release]; - - currentType = SBJsonStreamParserObject; - } } - (void)handleObjectEnd: (sbjson_token_t) tok { self.state = [stateStack lastObject]; [stateStack removeLastObject]; [state parser:self shouldTransitionTo:tok]; - - if (depth-- > levelsToSkip) { - id value = [dict retain]; - [self pop]; - [self parserFoundObject:value]; - [value release]; - } + [delegate parserFoundObjectEnd:self]; } - (void)handleArrayStart { - - if (depth >= maxDepth) { + if (stateStack.count >= maxDepth) { [self maxDepthError]; return; } - + + [delegate parserFoundArrayStart:self]; [stateStack addObject:state]; self.state = [SBJsonStreamParserStateArrayStart sharedInstance]; - - if (++depth > levelsToSkip) { - array = [[NSMutableArray alloc] init]; - [stack addObject:array]; - [array release]; - - currentType = SBJsonStreamParserArray; - } } - (void)handleArrayEnd: (sbjson_token_t) tok { self.state = [stateStack lastObject]; [stateStack removeLastObject]; [state parser:self shouldTransitionTo:tok]; - - if (depth-- > levelsToSkip) { - id value = [array retain]; - [self pop]; - [self parserFoundObject:value]; - [value release]; - } + [delegate parserFoundArrayEnd:self]; } - (void) handleTokenNotExpectedHere: (sbjson_token_t) tok { @@ -314,30 +218,30 @@ - (SBJsonStreamParserStatus)parse:(NSData *)data_ { break; case sbjson_token_true: - [self parserFoundObject:kTrue]; + [delegate parser:self foundBoolean:YES]; [state parser:self shouldTransitionTo:tok]; break; case sbjson_token_false: - [self parserFoundObject:kFalse]; + [delegate parser:self foundBoolean:NO]; [state parser:self shouldTransitionTo:tok]; break; case sbjson_token_null: - [self parserFoundObject:kNull]; + [delegate parserFoundNull:self]; [state parser:self shouldTransitionTo:tok]; break; case sbjson_token_number: - [self parserFoundObject:token]; + [delegate parser:self foundNumber:(NSNumber*)token]; [state parser:self shouldTransitionTo:tok]; break; case sbjson_token_string: if ([state needKey]) - [keyStack addObject:token]; + [delegate parser:self foundObjectKey:(NSString*)token]; else - [self parserFoundObject:token]; + [delegate parser:self foundString:(NSString*)token]; [state parser:self shouldTransitionTo:tok]; break; @@ -350,5 +254,4 @@ - (SBJsonStreamParserStatus)parse:(NSData *)data_ { return SBJsonStreamParserComplete; } - @end diff --git a/Classes/SBJsonStreamParserAccumulator.h b/Classes/SBJsonStreamParserAccumulator.h index 9311bd5a..141d6eed 100644 --- a/Classes/SBJsonStreamParserAccumulator.h +++ b/Classes/SBJsonStreamParserAccumulator.h @@ -28,9 +28,9 @@ */ #import -#import "SBJsonStreamParser.h" +#import "SBJsonStreamParserAdapter.h" -@interface SBJsonStreamParserAccumulator : NSObject +@interface SBJsonStreamParserAccumulator : NSObject @property (copy) id value; diff --git a/Classes/SBJsonStreamParserAccumulator.m b/Classes/SBJsonStreamParserAccumulator.m index 979ccb61..72716daf 100644 --- a/Classes/SBJsonStreamParserAccumulator.m +++ b/Classes/SBJsonStreamParserAccumulator.m @@ -38,7 +38,7 @@ - (void)dealloc { [super dealloc]; } -#pragma mark SBJsonStreamParserDelegate +#pragma mark SBJsonStreamParserAdapterDelegate - (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray *)array { value = [array retain]; diff --git a/Classes/SBJsonStreamParserAdapter.h b/Classes/SBJsonStreamParserAdapter.h new file mode 100644 index 00000000..d030d721 --- /dev/null +++ b/Classes/SBJsonStreamParserAdapter.h @@ -0,0 +1,147 @@ +/* + 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 +#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 { +@private + NSUInteger depth; + NSMutableArray *array; + 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 delegate; + +@end diff --git a/Classes/SBJsonStreamParserAdapter.m b/Classes/SBJsonStreamParserAdapter.m new file mode 100644 index 00000000..003dceb6 --- /dev/null +++ b/Classes/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 > self.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-- > self.levelsToSkip) { + id value = [dict retain]; + [self pop]; + [self parser:parser found:value]; + [value release]; + } +} + +- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser { + if (++depth > self.levelsToSkip) { + array = [[NSMutableArray new] autorelease]; + [stack addObject:array]; + currentType = SBJsonStreamParserAdapterArray; + } +} + +- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser { + if (depth-- > self.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 diff --git a/Examples/TweetStream/TweetStream/TweetStreamViewController.h b/Examples/TweetStream/TweetStream/TweetStreamViewController.h index ea68f325..6ed1d6bf 100644 --- a/Examples/TweetStream/TweetStream/TweetStreamViewController.h +++ b/Examples/TweetStream/TweetStream/TweetStreamViewController.h @@ -7,15 +7,18 @@ // #import -#import - -@interface TweetStreamViewController : UIViewController { + +@class SBJsonStreamParser; +@class SBJsonStreamParserAdapter; + +@interface TweetStreamViewController : UIViewController { IBOutlet UITextField *username; IBOutlet UITextField *password; IBOutlet UITextView *tweet; NSURLConnection *theConnection; SBJsonStreamParser *parser; + SBJsonStreamParserAdapter *adapter; } - (IBAction)go; diff --git a/Examples/TweetStream/TweetStream/TweetStreamViewController.m b/Examples/TweetStream/TweetStream/TweetStreamViewController.m index 54d595fa..397d8d5f 100644 --- a/Examples/TweetStream/TweetStream/TweetStreamViewController.m +++ b/Examples/TweetStream/TweetStream/TweetStreamViewController.m @@ -9,8 +9,12 @@ #import "TweetStreamViewController.h" #import +@interface TweetStreamViewController () +@end + @implementation TweetStreamViewController + - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. @@ -42,16 +46,25 @@ - (IBAction)go { [username resignFirstResponder]; [password resignFirstResponder]; - // Instantiate a stream parser. + // We don't want *all* the individual messages from the + // SBJsonStreamParser, just the top-level objects. The stream + // parser adapter exists for this purpose. + adapter = [[SBJsonStreamParserAdapter alloc] init]; + + // Set ourselves as the delegate, so we receive the messages + // from the adapter. + adapter.delegate = self; + + // Create a new stream parser.. parser = [[SBJsonStreamParser alloc] init]; - // Set ourselves as the delegate, so we receive messages from the parser. - parser.delegate = self; - + // .. and set our adapter as its delegate. + parser.delegate = adapter; + // Normally it's an error if JSON is followed by anything but // whitespace. Setting this means that the parser will be - // expecting the stream to contain multiple (possibly white-space separated) - // documents. + // expecting the stream to contain multiple whitespace-separated + // JSON documents. parser.supportMultipleDocuments = YES; NSString *url = @"http://stream.twitter.com/1/statuses/sample.json"; @@ -63,7 +76,7 @@ - (IBAction)go { theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; } -#pragma mark SBJsonStreamParserDelegate methods +#pragma mark SBJsonStreamParserAdapterDelegate methods - (void)parser:(SBJsonStreamParser *)parser foundArray:(NSArray *)array { [NSException raise:@"unexpected" format:@"Should not get here"]; diff --git a/Readme.md b/Readme.md index f3e55da5..021890b0 100644 --- a/Readme.md +++ b/Readme.md @@ -3,12 +3,26 @@ SBJson (aka json-framework) JSON (JavaScript Object Notation) is a light-weight data interchange format that's easy to read and write for humans and computers alike. This library implements strict JSON parsing and generation in Objective-C. -### Streaming JSON Support +New Features, Changes, and Notable Enhancements in 3.0 +------------------------------------------------------ -SBJson supports parsing of documents chunk-by-chunk, suitable for use with *NSURLConnection*. This means you can start parsing a JSON document before it is fully downloaded. Depending how you configure the delegates you can choose to have the entire document delivered to your process when it's finished parsing, or delivered bit-by-bit as records on a particular depth finishes downloading. For more details see *SBJsonStreamParser* in the [API docs][api]. +### JSON Stream Support + +We now support parsing of documents split into several NSData chunks, like those returned by *NSURLConnection*. This means you can start parsing a JSON document before it is fully downloaded. Depending how you configure the delegates you can chose to have the entire document delivered to your process when it's finished parsing, or delivered bit-by-bit as records on a particular level finishes downloading. For more details see *SBJsonStreamParser* and *SBJsonStreamParserAdapter* in the [API docs][api]. There is also support for *writing to* JSON streams. This means you can write huge JSON documents to disk, or an HTTP destination, without having to hold the entire structure in memory. You can use this to generate a stream of tick data for a stock trading simulation, for example. For more information see *SBJsonStreamWriter* in the [API docs][api]. +### Parse and write UTF8-encoded NSData + +The internals of *SBJsonParser* and *SBJsonWriter* have been rewritten to be NSData based. It is no longer necessary to convert data returned by NSURLConnection into an NSString before feeding it to the parser. The old NSString-oriented API methods still exists, but now converts their inputs to NSData objects and delegates to the new methods. + +### Project renamed to SBJson + +The project was renamed to avoid clashing with Apple's private JSON.framework. (And to make it easier to Google for.) + +* If you copy the classes into your project then all you need to update is to change the header inclusion from `#import "JSON.h"` to `#import "SBJson.h"`. +* If you link to the library rather than copy the classes you have to change the library you link to. On the Mac `JSON.framework` became `SBJson.framework`. On iOS `libjson.a` became `libsbjson-ios.a`. In both cases you now have to `#import ` in your code. + ### API documentation integrated with Xcode The *InstallDocumentation.sh* script allows you to generate [API documentation][api] from the source and install it into Xcode, so it's always at your fingertips. (This script requires [Doxygen][] to be installed.) After running the script from the top-level directory, open Xcode's documentation window and search for SBJson. (You might have to close and re-open Xcode for the changes to take effect.) @@ -53,7 +67,11 @@ If you're upgrading from a previous version, make sure you're deleting the old S Linking rather than copying --------------------------- -Copying the SBJson classes into your project isn't the only way to use this framework. (Though it is the simplest.) With Xcode 4's workspaces it has become much simpler to link to dependant projects. Linking is required if you want to use it with ARC (Automatic Reference Counting). The two examples in this distribution shows how to do the linking, for both Mac and iOS projects. +Copying the SBJson classes into your project isn't the only way to use this framework. (Though it is the simplest.) With Xcode 4's workspaces it has become much simpler to link to dependant projects. The examples in the distribution link to the iOS library and Mac framework, respectively. + +* [Linking to JSON Framework on iOS](http://github.com/stig/JsonSampleIPhone) +* [Linking to JSON Framework on the Mac](http://github.com/stig/JsonSampleMac) + Links ===== diff --git a/SBJson.xcodeproj/project.pbxproj b/SBJson.xcodeproj/project.pbxproj index 6e8fe208..371015a4 100644 --- a/SBJson.xcodeproj/project.pbxproj +++ b/SBJson.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ BC8851451391D6CD00370E55 /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88512B1391D6CD00370E55 /* SBJsonStreamParser.m */; }; BC8851461391D6CD00370E55 /* SBJsonStreamParserAccumulator.h in Headers */ = {isa = PBXBuildFile; fileRef = BC88512C1391D6CD00370E55 /* SBJsonStreamParserAccumulator.h */; settings = {ATTRIBUTES = (Public, ); }; }; BC8851471391D6CD00370E55 /* SBJsonStreamParserAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88512D1391D6CD00370E55 /* SBJsonStreamParserAccumulator.m */; }; + BC8851481391D6CD00370E55 /* SBJsonStreamParserAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = BC88512E1391D6CD00370E55 /* SBJsonStreamParserAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC8851491391D6CD00370E55 /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88512F1391D6CD00370E55 /* SBJsonStreamParserAdapter.m */; }; BC88514A1391D6CD00370E55 /* SBJsonStreamParserState.h in Headers */ = {isa = PBXBuildFile; fileRef = BC8851301391D6CD00370E55 /* SBJsonStreamParserState.h */; }; BC88514B1391D6CD00370E55 /* SBJsonStreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8851311391D6CD00370E55 /* SBJsonStreamParserState.m */; }; BC88514C1391D6CD00370E55 /* SBJsonStreamWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = BC8851321391D6CD00370E55 /* SBJsonStreamWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -54,6 +56,7 @@ BCADB7D0139210C90057A705 /* SBJsonParser.h in Headers */ = {isa = PBXBuildFile; fileRef = BC8851281391D6CD00370E55 /* SBJsonParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCADB7D1139210C90057A705 /* SBJsonStreamParser.h in Headers */ = {isa = PBXBuildFile; fileRef = BC88512A1391D6CD00370E55 /* SBJsonStreamParser.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCADB7D2139210C90057A705 /* SBJsonStreamParserAccumulator.h in Headers */ = {isa = PBXBuildFile; fileRef = BC88512C1391D6CD00370E55 /* SBJsonStreamParserAccumulator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BCADB7D3139210C90057A705 /* SBJsonStreamParserAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = BC88512E1391D6CD00370E55 /* SBJsonStreamParserAdapter.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCADB7D4139210C90057A705 /* SBJsonStreamParserState.h in Headers */ = {isa = PBXBuildFile; fileRef = BC8851301391D6CD00370E55 /* SBJsonStreamParserState.h */; }; BCADB7D5139210C90057A705 /* SBJsonStreamWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = BC8851321391D6CD00370E55 /* SBJsonStreamWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCADB7D6139210C90057A705 /* SBJsonStreamWriterAccumulator.h in Headers */ = {isa = PBXBuildFile; fileRef = BC8851341391D6CD00370E55 /* SBJsonStreamWriterAccumulator.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -71,6 +74,7 @@ BCC2628E13921035003D9994 /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8851291391D6CD00370E55 /* SBJsonParser.m */; }; BCC2628F13921035003D9994 /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88512B1391D6CD00370E55 /* SBJsonStreamParser.m */; }; BCC2629013921035003D9994 /* SBJsonStreamParserAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88512D1391D6CD00370E55 /* SBJsonStreamParserAccumulator.m */; }; + BCC2629113921035003D9994 /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = BC88512F1391D6CD00370E55 /* SBJsonStreamParserAdapter.m */; }; BCC2629213921035003D9994 /* SBJsonStreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8851311391D6CD00370E55 /* SBJsonStreamParserState.m */; }; BCC2629313921035003D9994 /* SBJsonStreamWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8851331391D6CD00370E55 /* SBJsonStreamWriter.m */; }; BCC2629413921035003D9994 /* SBJsonStreamWriterAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8851351391D6CD00370E55 /* SBJsonStreamWriterAccumulator.m */; }; @@ -115,6 +119,8 @@ BC88512B1391D6CD00370E55 /* SBJsonStreamParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParser.m; sourceTree = ""; }; BC88512C1391D6CD00370E55 /* SBJsonStreamParserAccumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserAccumulator.h; sourceTree = ""; }; BC88512D1391D6CD00370E55 /* SBJsonStreamParserAccumulator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserAccumulator.m; sourceTree = ""; }; + BC88512E1391D6CD00370E55 /* SBJsonStreamParserAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserAdapter.h; sourceTree = ""; }; + BC88512F1391D6CD00370E55 /* SBJsonStreamParserAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserAdapter.m; sourceTree = ""; }; BC8851301391D6CD00370E55 /* SBJsonStreamParserState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserState.h; sourceTree = ""; }; BC8851311391D6CD00370E55 /* SBJsonStreamParserState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserState.m; sourceTree = ""; }; BC8851321391D6CD00370E55 /* SBJsonStreamWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamWriter.h; sourceTree = ""; }; @@ -301,6 +307,8 @@ BC88512B1391D6CD00370E55 /* SBJsonStreamParser.m */, BC88512C1391D6CD00370E55 /* SBJsonStreamParserAccumulator.h */, BC88512D1391D6CD00370E55 /* SBJsonStreamParserAccumulator.m */, + BC88512E1391D6CD00370E55 /* SBJsonStreamParserAdapter.h */, + BC88512F1391D6CD00370E55 /* SBJsonStreamParserAdapter.m */, ); name = Parser; sourceTree = ""; @@ -377,6 +385,7 @@ BC8851421391D6CD00370E55 /* SBJsonParser.h in Headers */, BC8851441391D6CD00370E55 /* SBJsonStreamParser.h in Headers */, BC8851461391D6CD00370E55 /* SBJsonStreamParserAccumulator.h in Headers */, + BC8851481391D6CD00370E55 /* SBJsonStreamParserAdapter.h in Headers */, BC88514C1391D6CD00370E55 /* SBJsonStreamWriter.h in Headers */, BC88514E1391D6CD00370E55 /* SBJsonStreamWriterAccumulator.h in Headers */, BC8851541391D6CD00370E55 /* SBJsonWriter.h in Headers */, @@ -396,6 +405,7 @@ BCADB7D0139210C90057A705 /* SBJsonParser.h in Headers */, BCADB7D9139210C90057A705 /* SBJsonWriter.h in Headers */, BCADB7D1139210C90057A705 /* SBJsonStreamParser.h in Headers */, + BCADB7D3139210C90057A705 /* SBJsonStreamParserAdapter.h in Headers */, BCADB7D5139210C90057A705 /* SBJsonStreamWriter.h in Headers */, BCADB7CF139210C90057A705 /* NSObject+SBJson.h in Headers */, BCADB7D2139210C90057A705 /* SBJsonStreamParserAccumulator.h in Headers */, @@ -577,6 +587,7 @@ BC8851431391D6CD00370E55 /* SBJsonParser.m in Sources */, BC8851451391D6CD00370E55 /* SBJsonStreamParser.m in Sources */, BC8851471391D6CD00370E55 /* SBJsonStreamParserAccumulator.m in Sources */, + BC8851491391D6CD00370E55 /* SBJsonStreamParserAdapter.m in Sources */, BC88514B1391D6CD00370E55 /* SBJsonStreamParserState.m in Sources */, BC88514D1391D6CD00370E55 /* SBJsonStreamWriter.m in Sources */, BC88514F1391D6CD00370E55 /* SBJsonStreamWriterAccumulator.m in Sources */, @@ -610,6 +621,7 @@ BCC2628E13921035003D9994 /* SBJsonParser.m in Sources */, BCC2628F13921035003D9994 /* SBJsonStreamParser.m in Sources */, BCC2629013921035003D9994 /* SBJsonStreamParserAccumulator.m in Sources */, + BCC2629113921035003D9994 /* SBJsonStreamParserAdapter.m in Sources */, BCC2629213921035003D9994 /* SBJsonStreamParserState.m in Sources */, BCC2629313921035003D9994 /* SBJsonStreamWriter.m in Sources */, BCC2629413921035003D9994 /* SBJsonStreamWriterAccumulator.m in Sources */, diff --git a/Tests/StreamParserIntegrationTest.m b/Tests/StreamParserIntegrationTest.m index d4fd6997..7a4cc1a8 100644 --- a/Tests/StreamParserIntegrationTest.m +++ b/Tests/StreamParserIntegrationTest.m @@ -34,8 +34,9 @@ #import #import -@interface StreamParserIntegrationTest : SenTestCase < SBJsonStreamParserDelegate> { +@interface StreamParserIntegrationTest : SenTestCase < SBJsonStreamParserAdapterDelegate> { SBJsonStreamParser *parser; + SBJsonStreamParserAdapter *adapter; NSUInteger arrayCount, objectCount; NSDirectoryEnumerator *files; NSString *path; @@ -45,8 +46,11 @@ @interface StreamParserIntegrationTest : SenTestCase < SBJsonStreamParserDelegat @implementation StreamParserIntegrationTest - (void)setUp { + adapter = [SBJsonStreamParserAdapter new]; + adapter.delegate = self; + parser = [SBJsonStreamParser new]; - parser.delegate = self; + parser.delegate = adapter; parser.supportMultipleDocuments = YES; arrayCount = objectCount = 0u; @@ -104,14 +108,14 @@ - (void)testSingleArray { } - (void)testSkipArray { - parser.levelsToSkip = 1; + adapter.levelsToSkip = 1; [self parseArrayOfObjects]; STAssertEquals(arrayCount, (NSUInteger)0, nil); STAssertEquals(objectCount, (NSUInteger)100, nil); } - (void)testSkipArrayAndObject { - parser.levelsToSkip = 2; + adapter.levelsToSkip = 2; [self parseArrayOfObjects]; STAssertEquals(arrayCount, (NSUInteger)200, nil); STAssertEquals(objectCount, (NSUInteger)0, nil);