/*
|
Copyright (c) 2010-2013, 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 "CSSBJson4StreamParser.h"
|
|
/**
|
Block called when the parser has parsed an item. This could be once
|
for each root document parsed, or once for each unwrapped root array element.
|
|
@param item contains the parsed item.
|
@param stop set to YES if you want the parser to stop
|
*/
|
typedef void (^CSSBJson4ValueBlock)(id item, BOOL* stop);
|
|
/**
|
Block called if an error occurs.
|
@param error the error.
|
*/
|
typedef void (^CSSBJson4ErrorBlock)(NSError* error);
|
|
/**
|
Block used to process parsed tokens as they are encountered. You can use this
|
to transform strings containing dates into NSDate, for example.
|
@param item the parsed token
|
@param path the JSON Path of the token
|
*/
|
typedef id (^CSSBJson4ProcessBlock)(id item, NSString* path);
|
|
|
/**
|
Parse one or more chunks 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.
|
|
JSON is mapped to Objective-C types in the following way:
|
|
- null -> NSNull
|
- string -> NSString
|
- array -> NSMutableArray
|
- object -> NSMutableDictionary
|
- true -> NSNumber's -numberWithBool:YES
|
- false -> NSNumber's -numberWithBool:NO
|
- number -> NSNumber
|
|
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.
|
|
Integers are parsed into either a `long long` or `unsigned long long`
|
type if they fit, else a `double` is used. All real & exponential numbers
|
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:
|
|
CSSBJson4ValueBlock block = ^(id v, BOOL *stop) {
|
BOOL isArray = [v isKindOfClass:[NSArray class]];
|
NSLog(@"Found: %@", isArray ? @"Array" : @"Object");
|
};
|
|
CSSBJson4ErrorBlock eh = ^(NSError* err) {
|
NSLog(@"OOPS: %@", err);
|
};
|
|
id parser = [CSCSSBJson4Parser multiRootParserWithBlock:block
|
errorHandler:eh];
|
|
// Note that this input contains multiple top-level JSON documents
|
id data = [@"[]{}" dataWithEncoding:NSUTF8StringEncoding];
|
[parser parse:data];
|
[parser parse:data];
|
|
The above example will print:
|
|
- Found: Array
|
- Found: Object
|
- Found: Array
|
- Found: Object
|
|
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 rootArrayItems to YES:
|
|
id parser = [CSCSSBJson4Parser unwrapRootArrayParserWithBlock:block
|
errorHandler:eh];
|
|
// Note that this input contains A SINGLE top-level document
|
id data = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
|
[parser parse:data];
|
|
@note Stream based parsing does mean that you lose some of the correctness
|
verification you would have with a parser that considered the entire input
|
before returning an answer. It is technically possible to have some parts
|
of a document returned *as if they were correct* but then encounter an error
|
in a later part of the document. You should keep this in mind when
|
considering whether it would suit your application.
|
|
|
*/
|
@interface CSSBJson4Parser : NSObject
|
|
/**
|
Create a JSON Parser.
|
|
This can be used to create a parser that accepts only one document, or one that parses
|
many documents any
|
|
@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.
|
|
@param allowMultiRoot Indicate that you are expecting multiple whitespace-separated
|
JSON documents, similar to what Twitter uses.
|
|
@param unwrapRootArray 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 eh Called if the parser encounters an error.
|
|
@see -unwrapRootArrayParserWithBlock:errorHandler:
|
@see -multiRootParserWithBlock:errorHandler:
|
@see -initWithBlock:processBlock:multiRoot:unwrapRootArray:maxDepth:errorHandler:
|
|
*/
|
+ (id)parserWithBlock:(CSSBJson4ValueBlock)block
|
allowMultiRoot:(BOOL)allowMultiRoot
|
unwrapRootArray:(BOOL)unwrapRootArray
|
errorHandler:(CSSBJson4ErrorBlock)eh;
|
|
|
/**
|
Create a JSON Parser that parses multiple whitespace separated documents.
|
This is useful for something like Twitter's feed, which gives you one JSON
|
document per line.
|
|
@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.
|
|
@param eh Called if the parser encounters an error.
|
|
@see +unwrapRootArrayParserWithBlock:errorHandler:
|
@see +parserWithBlock:allowMultiRoot:unwrapRootArray:errorHandler:
|
@see -initWithBlock:processBlock:multiRoot:unwrapRootArray:maxDepth:errorHandler:
|
*/
|
+ (id)multiRootParserWithBlock:(CSSBJson4ValueBlock)block
|
errorHandler:(CSSBJson4ErrorBlock)eh;
|
|
/**
|
Create a JSON Parser that parses a huge array and calls for the value block for
|
each element in the outermost array.
|
|
@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.
|
|
@param eh Called if the parser encounters an error.
|
|
@see +multiRootParserWithBlock:errorHandler:
|
@see +parserWithBlock:allowMultiRoot:unwrapRootArray:errorHandler:
|
@see -initWithBlock:processBlock:multiRoot:unwrapRootArray:maxDepth:errorHandler:
|
*/
|
+ (id)unwrapRootArrayParserWithBlock:(CSSBJson4ValueBlock)block
|
errorHandler:(CSSBJson4ErrorBlock)eh;
|
|
/**
|
Create a JSON 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.
|
|
@param processBlock A block that allows you to process individual values before being
|
returned.
|
|
@param multiRoot Indicate that you are expecting multiple whitespace-separated
|
JSON documents, similar to what Twitter uses.
|
|
@param unwrapRootArray 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.
|
|
@param eh Called if the parser encounters an error.
|
|
*/
|
- (id)initWithBlock:(CSSBJson4ValueBlock)block
|
processBlock:(CSSBJson4ProcessBlock)processBlock
|
multiRoot:(BOOL)multiRoot
|
unwrapRootArray:(BOOL)unwrapRootArray
|
maxDepth:(NSUInteger)maxDepth
|
errorHandler:(CSSBJson4ErrorBlock)eh;
|
|
/**
|
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
|
- CSCSSBJson4ParserComplete if a full document was found
|
- CSCSSBJson4ParserWaitingForData if a partial document was found and more data is required to complete it
|
- CSCSSBJson4ParserError if an error occurred.
|
|
*/
|
- (CSSBJson4ParserStatus)parse:(NSData*)data;
|
|
@end
|