Skip to content

Commit

Permalink
Make SBJsonChunkParser "immutable" by removing properties
Browse files Browse the repository at this point in the history
  • Loading branch information
stig committed Nov 14, 2013
1 parent 78eb24c commit 4ef698e
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 81 deletions.
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ Features

SBJson's number one feature is chunk-based parsing. An example best sums it up:

SBJsonChunkParser *parser = [[SBJsonChunkParser alloc] initWithBlock:^(id v, BOOL *stop) {
SBEnumeratorBlock block = ^(id v, BOOL *stop) {
NSLog(@"Found: %@", @([v isKindOfClass:[NSArray class]]));
} errorHandler: ^(NSError* err) {
};
SBErrorHandlerBlock eh = ^(NSError* err) {
NSLog(@"OOPS: %@", err);
}];
parser.supportManyDocuments = YES;
}

id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:YES
arrayItems:NO
errorHandler:eh];

// Note that this input contains multiple top-level JSON documents
NSData *json = [@"[]{}[]{}" dataWithEncoding:NSUTF8StringEncoding];
Expand All @@ -30,12 +35,10 @@ Sometimes you just get a single mammoth array containing lots of smaller
documents. In that case you can get the same effect by setting
supportPartialDocuments to YES:

SBJsonChunkParser *parser = [[SBJsonChunkParser alloc] initWithBlock:^(id v, BOOL *stop) {
NSLog(@"Found: %@", @([v isKindOfClass:[NSArray class]]));
} errorHandler: ^(NSError* err) {
NSLog(@"OOPS: %@", err);
}];
parser.supportPartialDocuments = YES;
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:NO
arrayItems:YES
errorHandler:eh];

// Note that this input contains A SINGLE top-level document
NSData *json = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
Expand Down
90 changes: 52 additions & 38 deletions src/main/objc/SBJsonChunkParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,22 @@ typedef id (^SBProcessBlock)(id, NSString*);
are represented using a `double`. Previous versions of this library used
an NSDecimalNumber in some cases, but this is no longer the case.
The default behaviour is that your passed-in block is only called once the entire input is parsed.
If you set supportManyDocuments to YES and your input contains multiple (whitespace limited)
JSON documents your block will be called for each document:
The default behaviour is that your passed-in block is only called once the
entire input is parsed. If you set supportManyDocuments to YES and your input
contains multiple (whitespace limited) JSON documents your block will be called
for each document:
SBJsonChunkParser *parser = [[SBJsonChunkParser alloc] initWithBlock:^(id v, BOOL *stop) {
SBEnumeratorBlock block = ^(id v, BOOL *stop) {
NSLog(@"Found: %@", @([v isKindOfClass:[NSArray class]]));
} errorHandler: ^(NSError* err) {
};
SBErrorHandlerBlock eh = ^(NSError* err) {
NSLog(@"OOPS: %@", err);
}];
}
parser.supportManyDocuments = YES;
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:YES
partialDocuments:NO
errorHandler:eh];
// Note that this input contains multiple top-level JSON documents
NSData *json = [@"[]{}[]{}" dataWithEncoding:NSUTF8StringEncoding];
Expand All @@ -94,16 +99,14 @@ typedef id (^SBProcessBlock)(id, NSString*);
- Found: YES
- Found: NO
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: if you are parsing a long array you can get the same effect by
setting supportPartialDocuments to YES:
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: if you are parsing a long array you can
get the same effect by setting supportPartialDocuments to YES:
SBJsonChunkParser *parser = [[SBJsonChunkParser alloc] initWithBlock:^(id v, BOOL *stop) {
NSLog(@"Found: %@", @([v isKindOfClass:[NSArray class]]));
} errorHandler: ^(NSError* err) {
NSLog(@"OOPS: %@", err);
}];
parser.supportPartialDocuments = YES;
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:NO
partialDocuments:YES
errorHandler:eh];
// Note that this input contains A SINGLE top-level document
NSData *json = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
Expand All @@ -112,44 +115,55 @@ typedef id (^SBProcessBlock)(id, NSString*);
*/
@interface SBJsonChunkParser : NSObject

- (id)initWithBlock:(SBEnumeratorBlock)block errorHandler:(SBErrorHandlerBlock)eh;
- (id)initWithBlock:(SBEnumeratorBlock)block processBlock:(SBProcessBlock)processBlock errorHandler:(SBErrorHandlerBlock)eh;

/**
Expect multiple documents separated by whitespace
Create a JSON Chunk Parser.
@param block Called for each element. Set *stop to `YES` if you have seen
enough and would like to skip the rest of the elements.
Normally the -parse: method returns SBJsonParserComplete when it's found a complete JSON document.
Attempting to parse any more data at that point is considered an error. ("Garbage after JSON".)
@param manyDocs Indicate that you are expecting multiple whitespace-separated
JSON documents, similar to what Twitter uses.
If you set this property to true the parser will never return SBJsonParserComplete. Rather,
once an object is completed it will expect another object to immediately follow, separated
only by (optional) whitespace.
@param arrayItems If set the parser will pretend an root array does not exist
and the enumerator block will be called once for each item in it. This option
does nothing if the the JSON has an object at its root.
If you set this to YES the -parser:found: delegate method will be called once for each document in your input.
@param eh Called if the parser encounters an error.
*/
@property(nonatomic) BOOL supportManyDocuments;
- (id)initWithBlock:(SBEnumeratorBlock)block
manyDocuments:(BOOL)manyDocs
arrayItems:(BOOL)arrayItems
errorHandler:(SBErrorHandlerBlock)eh;


/**
Support partial documents.
Designated initialiser.
This is useful for parsing huge JSON documents, or documents coming in over a very slow link.
@param block Called for each element. Set *stop to `YES` if you have seen
enough and would like to skip the rest of the elements.
If you set this to true the outer array will be ignored and -parser:found: is called once
for each item in it.
@param processBlock A block that allows you to process individual values before being
returned.
*/
@property(nonatomic) BOOL supportPartialDocuments;
@param manyDocs Indicate that you are expecting multiple whitespace-separated
JSON documents, similar to what Twitter uses.
/**
The max parse depth
@param arrayItems If set the parser will pretend an root array does not exist
and the enumerator block will be called once for each item in it. This option
does nothing if the the JSON has an object at its root.
@param maxDepth The max recursion depth of the parser. Defaults to 32.
If the input is nested deeper than this the parser will halt parsing and return an error.
@param eh Called if the parser encounters an error.
Defaults to 32.
*/
@property(nonatomic) NSUInteger maxDepth;
- (id)initWithBlock:(SBEnumeratorBlock)block
processBlock:(SBProcessBlock)processBlock
manyDocuments:(BOOL)manyDocs
arrayItems:(BOOL)arrayItems
maxDepth:(NSUInteger)maxDepth
errorHandler:(SBErrorHandlerBlock)eh;

/**
Parse some JSON
Expand Down
30 changes: 23 additions & 7 deletions src/main/objc/SBJsonChunkParser.m
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,39 @@ @implementation SBJsonChunkParser {
SBErrorHandlerBlock errorHandler;
SBEnumeratorBlock valueBlock;
SBJsonChunkType currentType;
BOOL supportManyDocuments;
BOOL supportPartialDocuments;
}

#pragma mark Housekeeping

- (id)init {
@throw @"Use -initWithBlock:errorHandler: instead";
@throw @"Not Implemented";
}

- (id)initWithBlock:(SBEnumeratorBlock)block errorHandler:(SBErrorHandlerBlock)eh {
return [self initWithBlock:block processBlock:nil errorHandler:eh];
- (id)initWithBlock:(SBEnumeratorBlock)block
manyDocuments:(BOOL)manyDocs
arrayItems:(BOOL)arrayItems
errorHandler:(SBErrorHandlerBlock)eh {
return [self initWithBlock:block processBlock:nil manyDocuments:manyDocs arrayItems:arrayItems maxDepth:32
errorHandler:eh];
}

- (id)initWithBlock:(SBEnumeratorBlock)block processBlock:(SBProcessBlock)initialProcessBlock errorHandler:(SBErrorHandlerBlock)eh {
- (id)initWithBlock:(SBEnumeratorBlock)block
processBlock:(SBProcessBlock)initialProcessBlock
manyDocuments:(BOOL)manyDocs
arrayItems:(BOOL)arrayItems
maxDepth:(NSUInteger)maxDepth
errorHandler:(SBErrorHandlerBlock)eh {

self = [super init];
if (self) {
_parser = [[SBJsonStreamParser alloc] init];
_parser.delegate = self;
_parser.maxDepth = maxDepth;

supportManyDocuments = manyDocs;
supportPartialDocuments = arrayItems;

valueBlock = block;
keyStack = [[NSMutableArray alloc] initWithCapacity:32];
Expand Down Expand Up @@ -174,7 +190,7 @@ - (void)parserFoundObjectEnd:(SBJsonStreamParser *)parser {

- (void)parserFoundArrayStart:(SBJsonStreamParser *)parser {
depth++;
if (depth > 1 || !self.supportPartialDocuments) {
if (depth > 1 || !supportPartialDocuments) {
if(path)
[self addToPath];
array = [NSMutableArray new];
Expand All @@ -185,7 +201,7 @@ - (void)parserFoundArrayStart:(SBJsonStreamParser *)parser {

- (void)parserFoundArrayEnd:(SBJsonStreamParser *)parser {
depth--;
if (depth > 1 || !self.supportPartialDocuments) {
if (depth > 1 || !supportPartialDocuments) {
id value = array;
[self pop];
[self parser:parser found:value];
Expand Down Expand Up @@ -233,7 +249,7 @@ - (NSString *)pathString {
}

- (BOOL)parserShouldSupportManyDocuments:(SBJsonStreamParser *)parser {
return self.supportManyDocuments;
return supportManyDocuments;
}

- (SBJsonParserStatus)parse:(NSData *)data {
Expand Down
22 changes: 10 additions & 12 deletions src/main/objc/SBJsonParser.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,17 @@ - (id)objectWithData:(NSData *)data processValuesWithBlock:(SBProcessBlock)proce
return nil;
}


__block id value = nil;
SBJsonChunkParser *parser = [[SBJsonChunkParser alloc]
initWithBlock:^(id v, BOOL *stop) {
value = v;
}
processBlock:processBlock
errorHandler:^(NSError *err) {
self.error = err.localizedDescription;
}];


parser.maxDepth = self.maxDepth;
SBJsonChunkParser *parser = [[SBJsonChunkParser alloc] initWithBlock:^(id v, BOOL *stop) {
value = v;
}
processBlock:processBlock
manyDocuments:NO
arrayItems:NO
maxDepth:self.maxDepth
errorHandler:^(NSError *err) {
self.error = err.localizedDescription;
}];

switch ([parser parse:data]) {
case SBJsonParserComplete:
Expand Down
49 changes: 35 additions & 14 deletions src/test/objc/StreamSuite.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,21 @@ @interface StreamSuite : SenTestCase
@end

@implementation StreamSuite {
SBJsonChunkParser *parser;
SBEnumeratorBlock block;
SBErrorHandlerBlock eh;
NSUInteger arrayCount, objectCount;
NSError *error;
}

- (void)setUp {
parser = [[SBJsonChunkParser alloc] initWithBlock:^(id obj, BOOL *stop) {
block = ^(id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class]])
arrayCount++;
else if ([obj isKindOfClass:[NSDictionary class]])
objectCount++;
} errorHandler:^(NSError *e) { error = e; }];
};

eh = ^(NSError *e) { error = e; };

arrayCount = objectCount = 0u;
}
Expand All @@ -59,7 +62,10 @@ - (void) testParsingWithShortWorkBuffer{
"consectetur adipiscing elit. Donec ultrices ornare gravida. Vestibulum"\
" ante ipsum primisin faucibus orci luctus et ultrices posuere\"}]";

parser.supportManyDocuments = NO;
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:NO
arrayItems:NO
errorHandler:eh];

SBJsonParserStatus status = SBJsonParserWaitingForData;
NSData* data = nil;
Expand All @@ -81,7 +87,10 @@ - (void) testParsingWithShortWorkBuffer{
this data incrementally.
*/
- (void)testMultipleDocuments {
parser.supportManyDocuments = YES;
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:YES
arrayItems:NO
errorHandler:eh];

NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *root = [[bundle resourcePath] stringByAppendingPathComponent:@"stream"];
Expand All @@ -103,7 +112,7 @@ - (void)testMultipleDocuments {
STAssertEquals(objectCount, (NSUInteger)98, nil);
}

- (void)parseArrayOfObjects {
- (void)parseArrayOfObjects:(SBJsonChunkParser *)parser {
[parser parse:[NSData dataWithBytes:"[" length:1]];
for (int i = 1;; i++) {
char *utf8 = "{\"foo\":[],\"bar\":[]}";
Expand All @@ -116,29 +125,41 @@ - (void)parseArrayOfObjects {
}

- (void)testSingleArray {
[self parseArrayOfObjects];
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:NO
arrayItems:NO
errorHandler:eh];

[self parseArrayOfObjects:parser];
STAssertEquals(arrayCount, (NSUInteger)1, nil);
STAssertEquals(objectCount, (NSUInteger)0, nil);
}

- (void)testSkipArray {
parser.supportPartialDocuments = YES;
[self parseArrayOfObjects];
id parser = [[SBJsonChunkParser alloc] initWithBlock:block
manyDocuments:NO
arrayItems:YES
errorHandler:eh];

[self parseArrayOfObjects:parser];
STAssertEquals(arrayCount, (NSUInteger)0, nil);
STAssertEquals(objectCount, (NSUInteger)100, nil);
}

- (void)testStop {
__block int count = 0;
__block NSMutableArray *ary = [NSMutableArray array];

parser = [[SBJsonChunkParser alloc] initWithBlock:^(id obj, BOOL *stop) {
SBEnumeratorBlock block2 = ^(id obj, BOOL *stop) {
[ary addObject:obj];
*stop = ++count >= 23;
} errorHandler:^(NSError *e) { error = e; }];
parser.supportPartialDocuments = YES;
};

id parser = [[SBJsonChunkParser alloc] initWithBlock:block2
manyDocuments:NO
arrayItems:YES
errorHandler:eh];

[self parseArrayOfObjects];
[self parseArrayOfObjects:parser];
STAssertEquals(ary.count, (NSUInteger)23, nil);
}

Expand Down

0 comments on commit 4ef698e

Please sign in to comment.