This commit is contained in:
wu736139669
2015-03-30 14:41:20 +08:00
parent 8cda2a3694
commit a1cea576f2
354 changed files with 60878 additions and 4 deletions
+35
View File
@@ -0,0 +1,35 @@
/*
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.
*/
#import "SBJson4Writer.h"
#import "SBJson4StreamParser.h"
#import "SBJson4Parser.h"
#import "SBJson4StreamWriter.h"
#import "SBJson4StreamTokeniser.h"
+248
View File
@@ -0,0 +1,248 @@
/*
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 "SBJson4StreamParser.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 (^SBJson4ValueBlock)(id item, BOOL* stop);
/**
Block called if an error occurs.
@param error the error.
*/
typedef void (^SBJson4ErrorBlock)(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 (^SBJson4ProcessBlock)(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:
SBJson4ValueBlock block = ^(id v, BOOL *stop) {
BOOL isArray = [v isKindOfClass:[NSArray class]];
NSLog(@"Found: %@", isArray ? @"Array" : @"Object");
}
SBJson4ErrorBlock eh = ^(NSError* err) {
NSLog(@"OOPS: %@", err);
}
id parser = [SBJson4Parser 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 = [SBJson4Parser 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 SBJson4Parser : 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:(SBJson4ValueBlock)block
allowMultiRoot:(BOOL)allowMultiRoot
unwrapRootArray:(BOOL)unwrapRootArray
errorHandler:(SBJson4ErrorBlock)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:(SBJson4ValueBlock)block
errorHandler:(SBJson4ErrorBlock)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:(SBJson4ValueBlock)block
errorHandler:(SBJson4ErrorBlock)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:(SBJson4ValueBlock)block
processBlock:(SBJson4ProcessBlock)processBlock
multiRoot:(BOOL)multiRoot
unwrapRootArray:(BOOL)unwrapRootArray
maxDepth:(NSUInteger)maxDepth
errorHandler:(SBJson4ErrorBlock)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
- SBJson4ParserComplete if a full document was found
- SBJson4ParserWaitingForData if a partial document was found and more data is required to complete it
- SBJson4ParserError if an error occured.
*/
- (SBJson4ParserStatus)parse:(NSData*)data;
@end
+292
View File
@@ -0,0 +1,292 @@
/*
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.
*/
#if !__has_feature(objc_arc)
#error "This source file must be compiled with ARC enabled!"
#endif
#import "SBJson4Parser.h"
@interface SBJson4Parser () <SBJson4StreamParserDelegate>
- (void)pop;
- (void)parser:(SBJson4StreamParser *)parser found:(id)obj;
@end
typedef enum {
SBJson4ChunkNone,
SBJson4ChunkArray,
SBJson4ChunkObject,
} SBJson4ChunkType;
@implementation SBJson4Parser {
SBJson4StreamParser *_parser;
NSUInteger depth;
NSMutableArray *array;
NSMutableDictionary *dict;
NSMutableArray *keyStack;
NSMutableArray *stack;
NSMutableArray *path;
SBJson4ProcessBlock processBlock;
SBJson4ErrorBlock errorHandler;
SBJson4ValueBlock valueBlock;
SBJson4ChunkType currentType;
BOOL supportManyDocuments;
BOOL supportPartialDocuments;
NSUInteger _maxDepth;
}
#pragma mark Housekeeping
- (id)init {
@throw @"Not Implemented";
}
+ (id)multiRootParserWithBlock:(SBJson4ValueBlock)block errorHandler:(SBJson4ErrorBlock)eh {
return [self parserWithBlock:block
allowMultiRoot:YES
unwrapRootArray:NO
errorHandler:eh];
}
+ (id)unwrapRootArrayParserWithBlock:(SBJson4ValueBlock)block errorHandler:(SBJson4ErrorBlock)eh {
return [self parserWithBlock:block
allowMultiRoot:NO
unwrapRootArray:YES
errorHandler:eh];
}
+ (id)parserWithBlock:(SBJson4ValueBlock)block
allowMultiRoot:(BOOL)allowMultiRoot
unwrapRootArray:(BOOL)unwrapRootArray
errorHandler:(SBJson4ErrorBlock)eh {
return [[self alloc] initWithBlock:block
processBlock:nil
multiRoot:allowMultiRoot
unwrapRootArray:unwrapRootArray
maxDepth:32
errorHandler:eh];
}
- (id)initWithBlock:(SBJson4ValueBlock)block
processBlock:(SBJson4ProcessBlock)initialProcessBlock
multiRoot:(BOOL)multiRoot
unwrapRootArray:(BOOL)unwrapRootArray
maxDepth:(NSUInteger)maxDepth
errorHandler:(SBJson4ErrorBlock)eh {
self = [super init];
if (self) {
_parser = [[SBJson4StreamParser alloc] init];
_parser.delegate = self;
supportManyDocuments = multiRoot;
supportPartialDocuments = unwrapRootArray;
valueBlock = block;
keyStack = [[NSMutableArray alloc] initWithCapacity:32];
stack = [[NSMutableArray alloc] initWithCapacity:32];
if (initialProcessBlock)
path = [[NSMutableArray alloc] initWithCapacity:32];
processBlock = initialProcessBlock;
errorHandler = eh ? eh : ^(NSError*err) { NSLog(@"%@", err); };
currentType = SBJson4ChunkNone;
_maxDepth = maxDepth;
}
return self;
}
#pragma mark Private methods
- (void)pop {
[stack removeLastObject];
array = nil;
dict = nil;
currentType = SBJson4ChunkNone;
id value = [stack lastObject];
if ([value isKindOfClass:[NSArray class]]) {
array = value;
currentType = SBJson4ChunkArray;
} else if ([value isKindOfClass:[NSDictionary class]]) {
dict = value;
currentType = SBJson4ChunkObject;
}
}
- (void)parser:(SBJson4StreamParser *)parser found:(id)obj {
[self parserFound:obj isValue:NO ];
}
- (void)parserFound:(id)obj isValue:(BOOL)isValue {
NSParameterAssert(obj);
if(processBlock&&path) {
if(isValue) {
obj = processBlock(obj,[NSString stringWithFormat:@"%@.%@",[self pathString],[keyStack lastObject]]);
}
else {
[path removeLastObject];
}
}
switch (currentType) {
case SBJson4ChunkArray:
[array addObject:obj];
break;
case SBJson4ChunkObject:
NSParameterAssert(keyStack.count);
[dict setObject:obj forKey:[keyStack lastObject]];
[keyStack removeLastObject];
break;
case SBJson4ChunkNone: {
__block BOOL stop = NO;
valueBlock(obj, &stop);
if (stop) [_parser stop];
}
break;
default:
break;
}
}
#pragma mark Delegate methods
- (void)parserFoundObjectStart {
++depth;
if (depth > _maxDepth)
[self maxDepthError];
if (path)
[self addToPath];
dict = [NSMutableDictionary new];
[stack addObject:dict];
currentType = SBJson4ChunkObject;
}
- (void)parserFoundObjectKey:(NSString *)key_ {
[keyStack addObject:key_];
}
- (void)parserFoundObjectEnd {
depth--;
id value = dict;
[self pop];
[self parser:_parser found:value];
}
- (void)parserFoundArrayStart {
depth++;
if (depth > _maxDepth)
[self maxDepthError];
if (depth > 1 || !supportPartialDocuments) {
if(path)
[self addToPath];
array = [NSMutableArray new];
[stack addObject:array];
currentType = SBJson4ChunkArray;
}
}
- (void)parserFoundArrayEnd {
depth--;
if (depth > 1 || !supportPartialDocuments) {
id value = array;
[self pop];
[self parser:_parser found:value];
}
}
- (void)maxDepthError {
id ui = @{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Input depth exceeds max depth of %lu", (unsigned long)_maxDepth]};
errorHandler([NSError errorWithDomain:@"org.sbjson.parser" code:3 userInfo:ui]);
[_parser stop];
}
- (void)parserFoundBoolean:(BOOL)x {
[self parserFound:[NSNumber numberWithBool:x] isValue:YES ];
}
- (void)parserFoundNull {
[self parserFound:[NSNull null] isValue:YES ];
}
- (void)parserFoundNumber:(NSNumber *)num {
[self parserFound:num isValue:YES ];
}
- (void)parserFoundString:(NSString *)string {
[self parserFound:string isValue:YES ];
}
- (void)parserFoundError:(NSError *)err {
errorHandler(err);
}
- (void)addToPath {
if([path count]==0)
[path addObject:@"$"];
else if([[stack lastObject] isKindOfClass:[NSArray class]])
[path addObject:@([[stack lastObject] count])];
else
[path addObject:[keyStack lastObject]];
}
- (NSString *)pathString {
NSMutableString *pathString = [NSMutableString stringWithString:@"$"];
for(NSUInteger i=1;i<[path count];i++) {
if([[path objectAtIndex:i] isKindOfClass:[NSNumber class]])
[pathString appendString:[NSString stringWithFormat:@"[%@]",[path objectAtIndex:i]]];
else
[pathString appendString:[NSString stringWithFormat:@".%@",[path objectAtIndex:i]]];
}
return pathString;
}
- (BOOL)parserShouldSupportManyDocuments {
return supportManyDocuments;
}
- (SBJson4ParserStatus)parse:(NSData *)data {
return [_parser parse:data];
}
@end
+132
View File
@@ -0,0 +1,132 @@
/*
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>
@class SBJson4StreamParser;
@class SBJson4StreamParserState;
typedef enum {
SBJson4ParserComplete,
SBJson4ParserStopped,
SBJson4ParserWaitingForData,
SBJson4ParserError,
} SBJson4ParserStatus;
/**
Delegate for interacting directly with the low-level parser
You will most likely find it much more convenient to use the SBJson4Parser instead.
*/
@protocol SBJson4StreamParserDelegate < NSObject >
/// Called when object start is found
- (void)parserFoundObjectStart;
/// Called when object key is found
- (void)parserFoundObjectKey:(NSString *)key;
/// Called when object end is found
- (void)parserFoundObjectEnd;
/// Called when array start is found
- (void)parserFoundArrayStart;
/// Called when array end is found
- (void)parserFoundArrayEnd;
/// Called when a boolean value is found
- (void)parserFoundBoolean:(BOOL)x;
/// Called when a null value is found
- (void)parserFoundNull;
/// Called when a number is found
- (void)parserFoundNumber:(NSNumber *)num;
/// Called when a string is found
- (void)parserFoundString:(NSString *)string;
/// Called when an error occurs
- (void)parserFoundError:(NSError *)err;
@optional
/// Called to determine whether to allow multiple whitespace-separated documents
- (BOOL)parserShouldSupportManyDocuments;
@end
/**
Low-level Stream parser
You most likely want to use the SBJson4Parser instead, but if you
really need low-level access to tokens one-by-one you can use this class.
*/
@interface SBJson4StreamParser : NSObject
@property (nonatomic, weak) SBJson4StreamParserState *state; // Private
@property (nonatomic, readonly, strong) NSMutableArray *stateStack; // Private
/**
Delegate to receive messages
The object set here receives a series of messages as the parser breaks down the JSON stream
into valid tokens.
Usually this should be an instance of SBJson4Parser, but you can
substitute your own implementation of the SBJson4StreamParserDelegate protocol if you need to.
*/
@property (nonatomic, weak) id<SBJson4StreamParserDelegate> delegate;
/**
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
- SBJson4ParserComplete if a full document was found
- SBJson4ParserWaitingForData if a partial document was found and more data is required to complete it
- SBJson4ParserError if an error occured.
*/
- (SBJson4ParserStatus)parse:(NSData*)data;
/**
Call this to cause parsing to stop.
*/
- (void)stop;
@end
+327
View File
@@ -0,0 +1,327 @@
/*
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.
*/
#if !__has_feature(objc_arc)
#error "This source file must be compiled with ARC enabled!"
#endif
#import "SBJson4StreamParser.h"
#import "SBJson4StreamTokeniser.h"
#import "SBJson4StreamParserState.h"
#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL))
@implementation SBJson4StreamParser {
SBJson4StreamTokeniser *tokeniser;
BOOL stopped;
}
#pragma mark Housekeeping
- (id)init {
self = [super init];
if (self) {
_stateStack = [[NSMutableArray alloc] initWithCapacity:32];
_state = [SBJson4StreamParserStateStart sharedInstance];
tokeniser = [[SBJson4StreamTokeniser alloc] init];
}
return self;
}
#pragma mark Methods
- (NSString*)tokenName:(sbjson4_token_t)token {
switch (token) {
case sbjson4_token_array_open:
return @"start of array";
break;
case sbjson4_token_array_close:
return @"end of array";
break;
case sbjson4_token_integer:
case sbjson4_token_real:
return @"number";
break;
case sbjson4_token_string:
case sbjson4_token_encoded:
return @"string";
break;
case sbjson4_token_bool:
return @"boolean";
break;
case sbjson4_token_null:
return @"null";
break;
case sbjson4_token_entry_sep:
return @"key-value separator";
break;
case sbjson4_token_value_sep:
return @"value separator";
break;
case sbjson4_token_object_open:
return @"start of object";
break;
case sbjson4_token_object_close:
return @"end of object";
break;
case sbjson4_token_eof:
case sbjson4_token_error:
break;
}
NSAssert(NO, @"Should not get here");
return @"<aaiiie!>";
}
- (void)handleObjectStart {
[_delegate parserFoundObjectStart];
[_stateStack addObject:_state];
_state = [SBJson4StreamParserStateObjectStart sharedInstance];
}
- (void)handleObjectEnd: (sbjson4_token_t) tok {
_state = [_stateStack lastObject];
[_stateStack removeLastObject];
[_state parser:self shouldTransitionTo:tok];
[_delegate parserFoundObjectEnd];
}
- (void)handleArrayStart {
[_delegate parserFoundArrayStart];
[_stateStack addObject:_state];
_state = [SBJson4StreamParserStateArrayStart sharedInstance];
}
- (void)handleArrayEnd: (sbjson4_token_t) tok {
_state = [_stateStack lastObject];
[_stateStack removeLastObject];
[_state parser:self shouldTransitionTo:tok];
[_delegate parserFoundArrayEnd];
}
- (void) handleTokenNotExpectedHere: (sbjson4_token_t) tok {
NSString *tokenName = [self tokenName:tok];
NSString *stateName = [_state name];
_state = [SBJson4StreamParserStateError sharedInstance];
id ui = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Token '%@' not expected %@", tokenName, stateName]};
[_delegate parserFoundError:[NSError errorWithDomain:@"org.sbjson.parser" code:2 userInfo:ui]];
}
- (SBJson4ParserStatus)parse:(NSData *)data_ {
@autoreleasepool {
[tokeniser appendData:data_];
for (;;) {
if (stopped)
return SBJson4ParserStopped;
if ([_state isError])
return SBJson4ParserError;
char *token;
NSUInteger token_len;
sbjson4_token_t tok = [tokeniser getToken:&token length:&token_len];
switch (tok) {
case sbjson4_token_eof:
return [_state parserShouldReturn:self];
break;
case sbjson4_token_error:
_state = [SBJson4StreamParserStateError sharedInstance];
[_delegate parserFoundError:[NSError errorWithDomain:@"org.sbjson.parser" code:3
userInfo:@{NSLocalizedDescriptionKey : tokeniser.error}]];
return SBJson4ParserError;
break;
default:
if (![_state parser:self shouldAcceptToken:tok]) {
[self handleTokenNotExpectedHere: tok];
return SBJson4ParserError;
}
switch (tok) {
case sbjson4_token_object_open:
[self handleObjectStart];
break;
case sbjson4_token_object_close:
[self handleObjectEnd: tok];
break;
case sbjson4_token_array_open:
[self handleArrayStart];
break;
case sbjson4_token_array_close:
[self handleArrayEnd: tok];
break;
case sbjson4_token_value_sep:
case sbjson4_token_entry_sep:
[_state parser:self shouldTransitionTo:tok];
break;
case sbjson4_token_bool:
[_delegate parserFoundBoolean:token[0] == 't'];
[_state parser:self shouldTransitionTo:tok];
break;
case sbjson4_token_null:
[_delegate parserFoundNull];
[_state parser:self shouldTransitionTo:tok];
break;
case sbjson4_token_integer: {
const int UNSIGNED_LONG_LONG_MAX_DIGITS = 20;
if (token_len <= UNSIGNED_LONG_LONG_MAX_DIGITS) {
if (*token == '-')
[_delegate parserFoundNumber:@(strtoll(token, NULL, 10))];
else
[_delegate parserFoundNumber:@(strtoull(token, NULL, 10))];
[_state parser:self shouldTransitionTo:tok];
break;
}
}
// FALLTHROUGH
case sbjson4_token_real: {
[_delegate parserFoundNumber:@(strtod(token, NULL))];
[_state parser:self shouldTransitionTo:tok];
break;
}
case sbjson4_token_string: {
NSString *string = [[NSString alloc] initWithBytes:token length:token_len encoding:NSUTF8StringEncoding];
if ([_state needKey])
[_delegate parserFoundObjectKey:string];
else
[_delegate parserFoundString:string];
[_state parser:self shouldTransitionTo:tok];
break;
}
case sbjson4_token_encoded: {
NSString *string = [self decodeStringToken:token length:token_len];
if ([_state needKey])
[_delegate parserFoundObjectKey:string];
else
[_delegate parserFoundString:string];
[_state parser:self shouldTransitionTo:tok];
break;
}
default:
break;
}
break;
}
}
return SBJson4ParserComplete;
}
}
- (unichar)decodeHexQuad:(char *)quad {
unichar ch = 0;
for (NSUInteger i = 0; i < 4; i++) {
int c = quad[i];
ch *= 16;
switch (c) {
case '0' ... '9': ch += c - '0'; break;
case 'a' ... 'f': ch += 10 + c - 'a'; break;
case 'A' ... 'F': ch += 10 + c - 'A'; break;
default: @throw @"FUT FUT FUT";
}
}
return ch;
}
- (NSString*)decodeStringToken:(char*)bytes length:(NSUInteger)len {
NSMutableData *buf = [NSMutableData dataWithCapacity:len];
for (NSUInteger i = 0; i < len;) {
switch ((unsigned char)bytes[i]) {
case '\\': {
switch ((unsigned char)bytes[++i]) {
case '"': [buf appendBytes:"\"" length:1]; i++; break;
case '/': [buf appendBytes:"/" length:1]; i++; break;
case '\\': [buf appendBytes:"\\" length:1]; i++; break;
case 'b': [buf appendBytes:"\b" length:1]; i++; break;
case 'f': [buf appendBytes:"\f" length:1]; i++; break;
case 'n': [buf appendBytes:"\n" length:1]; i++; break;
case 'r': [buf appendBytes:"\r" length:1]; i++; break;
case 't': [buf appendBytes:"\t" length:1]; i++; break;
case 'u': {
unichar hi = [self decodeHexQuad:bytes + i + 1];
i += 5;
if (SBStringIsSurrogateHighCharacter(hi)) {
// Skip past \u that we know is there..
unichar lo = [self decodeHexQuad:bytes + i + 2];
i += 6;
[buf appendData:[[NSString stringWithFormat:@"%C%C", hi, lo] dataUsingEncoding:NSUTF8StringEncoding]];
} else {
[buf appendData:[[NSString stringWithFormat:@"%C", hi] dataUsingEncoding:NSUTF8StringEncoding]];
}
break;
}
default: @throw @"FUT FUT FUT";
}
break;
}
default:
[buf appendBytes:bytes + i length:1];
i++;
break;
}
}
return [[NSString alloc] initWithData:buf encoding:NSUTF8StringEncoding];
}
- (void)stop {
stopped = YES;
}
@end
+82
View File
@@ -0,0 +1,82 @@
/*
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 "SBJson4StreamTokeniser.h"
#import "SBJson4StreamParser.h"
@interface SBJson4StreamParserState : NSObject
+ (id)sharedInstance;
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token;
- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser;
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok;
- (BOOL)needKey;
- (BOOL)isError;
- (NSString*)name;
@end
@interface SBJson4StreamParserStateStart : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateComplete : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateError : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateObjectStart : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateObjectGotKey : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateObjectSeparator : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateObjectGotValue : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateObjectNeedKey : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateArrayStart : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateArrayGotValue : SBJson4StreamParserState
@end
@interface SBJson4StreamParserStateArrayNeedValue : SBJson4StreamParserState
@end
+364
View File
@@ -0,0 +1,364 @@
/*
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.
*/
#if !__has_feature(objc_arc)
#error "This source file must be compiled with ARC enabled!"
#endif
#import "SBJson4StreamParserState.h"
#define SINGLETON \
+ (id)sharedInstance { \
static id state = nil; \
if (!state) { \
@synchronized(self) { \
if (!state) state = [[self alloc] init]; \
} \
} \
return state; \
}
@implementation SBJson4StreamParserState
+ (id)sharedInstance { return nil; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
return NO;
}
- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser {
return SBJson4ParserWaitingForData;
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {}
- (BOOL)needKey {
return NO;
}
- (NSString*)name {
return @"<aaiie!>";
}
- (BOOL)isError {
return NO;
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateStart
SINGLETON
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
return token == sbjson4_token_array_open || token == sbjson4_token_object_open;
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
SBJson4StreamParserState *state = nil;
switch (tok) {
case sbjson4_token_array_open:
state = [SBJson4StreamParserStateArrayStart sharedInstance];
break;
case sbjson4_token_object_open:
state = [SBJson4StreamParserStateObjectStart sharedInstance];
break;
case sbjson4_token_array_close:
case sbjson4_token_object_close:
if ([parser.delegate respondsToSelector:@selector(parserShouldSupportManyDocuments)] && [parser.delegate parserShouldSupportManyDocuments])
state = parser.state;
else
state = [SBJson4StreamParserStateComplete sharedInstance];
break;
case sbjson4_token_eof:
return;
default:
state = [SBJson4StreamParserStateError sharedInstance];
break;
}
parser.state = state;
}
- (NSString*)name { return @"before outer-most array or object"; }
@end
#pragma mark -
@implementation SBJson4StreamParserStateComplete
SINGLETON
- (NSString*)name { return @"after outer-most array or object"; }
- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser {
return SBJson4ParserComplete;
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateError
SINGLETON
- (NSString*)name { return @"in error"; }
- (SBJson4ParserStatus)parserShouldReturn:(SBJson4StreamParser *)parser {
return SBJson4ParserError;
}
- (BOOL)isError {
return YES;
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateObjectStart
SINGLETON
- (NSString*)name { return @"at beginning of object"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
switch (token) {
case sbjson4_token_object_close:
case sbjson4_token_string:
case sbjson4_token_encoded:
return YES;
break;
default:
return NO;
break;
}
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateObjectGotKey sharedInstance];
}
- (BOOL)needKey {
return YES;
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateObjectGotKey
SINGLETON
- (NSString*)name { return @"after object key"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
return token == sbjson4_token_entry_sep;
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateObjectSeparator sharedInstance];
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateObjectSeparator
SINGLETON
- (NSString*)name { return @"as object value"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
switch (token) {
case sbjson4_token_object_open:
case sbjson4_token_array_open:
case sbjson4_token_bool:
case sbjson4_token_null:
case sbjson4_token_integer:
case sbjson4_token_real:
case sbjson4_token_string:
case sbjson4_token_encoded:
return YES;
break;
default:
return NO;
break;
}
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateObjectGotValue sharedInstance];
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateObjectGotValue
SINGLETON
- (NSString*)name { return @"after object value"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
switch (token) {
case sbjson4_token_object_close:
case sbjson4_token_value_sep:
return YES;
break;
default:
return NO;
break;
}
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateObjectNeedKey sharedInstance];
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateObjectNeedKey
SINGLETON
- (NSString*)name { return @"in place of object key"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
return sbjson4_token_string == token || sbjson4_token_encoded == token;
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateObjectGotKey sharedInstance];
}
- (BOOL)needKey {
return YES;
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateArrayStart
SINGLETON
- (NSString*)name { return @"at array start"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
switch (token) {
case sbjson4_token_object_close:
case sbjson4_token_entry_sep:
case sbjson4_token_value_sep:
return NO;
break;
default:
return YES;
break;
}
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateArrayGotValue sharedInstance];
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateArrayGotValue
SINGLETON
- (NSString*)name { return @"after array value"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
return token == sbjson4_token_array_close || token == sbjson4_token_value_sep;
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
if (tok == sbjson4_token_value_sep)
parser.state = [SBJson4StreamParserStateArrayNeedValue sharedInstance];
}
@end
#pragma mark -
@implementation SBJson4StreamParserStateArrayNeedValue
SINGLETON
- (NSString*)name { return @"as array value"; }
- (BOOL)parser:(SBJson4StreamParser *)parser shouldAcceptToken:(sbjson4_token_t)token {
switch (token) {
case sbjson4_token_array_close:
case sbjson4_token_entry_sep:
case sbjson4_token_object_close:
case sbjson4_token_value_sep:
return NO;
break;
default:
return YES;
break;
}
}
- (void)parser:(SBJson4StreamParser *)parser shouldTransitionTo:(sbjson4_token_t)tok {
parser.state = [SBJson4StreamParserStateArrayGotValue sharedInstance];
}
@end
+40
View File
@@ -0,0 +1,40 @@
//
// Created by SuperPappi on 09/01/2013.
//
// To change the template use AppCode | Preferences | File Templates.
//
#import <Foundation/Foundation.h>
typedef enum {
sbjson4_token_error = -1,
sbjson4_token_eof,
sbjson4_token_array_open,
sbjson4_token_array_close,
sbjson4_token_value_sep,
sbjson4_token_object_open,
sbjson4_token_object_close,
sbjson4_token_entry_sep,
sbjson4_token_bool,
sbjson4_token_null,
sbjson4_token_integer,
sbjson4_token_real,
sbjson4_token_string,
sbjson4_token_encoded,
} sbjson4_token_t;
@interface SBJson4StreamTokeniser : NSObject
@property (nonatomic, readonly, copy) NSString *error;
- (void)appendData:(NSData*)data_;
- (sbjson4_token_t)getToken:(char**)tok length:(NSUInteger*)len;
@end
+397
View File
@@ -0,0 +1,397 @@
//
// Created by SuperPappi on 09/01/2013.
//
// To change the template use AppCode | Preferences | File Templates.
//
#import "SBJson4StreamTokeniser.h"
#define SBStringIsIllegalSurrogateHighCharacter(character) (((character) >= 0xD800UL) && ((character) <= 0xDFFFUL))
#define SBStringIsSurrogateLowCharacter(character) ((character >= 0xDC00UL) && (character <= 0xDFFFUL))
#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL))
@implementation SBJson4StreamTokeniser {
NSMutableData *data;
const char *bytes;
NSUInteger index;
NSUInteger offset;
}
- (void)setError:(NSString *)error {
_error = [NSString stringWithFormat:@"%@ at index %lu", error, (unsigned long)(offset + index)];
}
- (void)appendData:(NSData *)data_ {
if (!data) {
data = [data_ mutableCopy];
} else if (index) {
// Discard data we've already parsed
[data replaceBytesInRange:NSMakeRange(0, index) withBytes:"" length:0];
[data appendData:data_];
// Add to the offset for reporting
offset += index;
// Reset index to point to current position
index = 0u;
}
else {
[data appendData:data_];
}
bytes = [data bytes];
}
- (void)skipWhitespace {
while (index < data.length) {
switch (bytes[index]) {
case ' ':
case '\t':
case '\r':
case '\n':
index++;
break;
default:
return;
}
}
}
- (BOOL)getUnichar:(unichar *)ch {
if ([self haveRemainingCharacters:1]) {
*ch = (unichar) bytes[index];
return YES;
}
return NO;
}
- (BOOL)haveOneMoreCharacter {
return [self haveRemainingCharacters:1];
}
- (BOOL)haveRemainingCharacters:(NSUInteger)length {
return data.length - index >= length;
}
- (sbjson4_token_t)match:(char *)str retval:(sbjson4_token_t)tok token:(char **)token length:(NSUInteger *)length {
NSUInteger len = strlen(str);
if ([self haveRemainingCharacters:len]) {
if (!memcmp(bytes + index, str, len)) {
*token = str;
*length = len;
index += len;
return tok;
}
[self setError: [NSString stringWithFormat:@"Expected '%s' after initial '%.1s'", str, str]];
return sbjson4_token_error;
}
return sbjson4_token_eof;
}
- (BOOL)decodeHexQuad:(unichar*)quad {
unichar tmp = 0;
for (int i = 0; i < 4; i++, index++) {
unichar c = bytes[index];
tmp *= 16;
switch (c) {
case '0' ... '9':
tmp += c - '0';
break;
case 'a' ... 'f':
tmp += 10 + c - 'a';
break;
case 'A' ... 'F':
tmp += 10 + c - 'A';
break;
default:
return NO;
}
}
*quad = tmp;
return YES;
}
- (sbjson4_token_t)getStringToken:(char **)token length:(NSUInteger *)length {
// Skip initial "
index++;
NSUInteger string_start = index;
sbjson4_token_t tok = sbjson4_token_string;
for (;;) {
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
switch (bytes[index]) {
case 0 ... 0x1F:
[self setError:[NSString stringWithFormat:@"Unescaped control character [0x%0.2X] in string", bytes[index]]];
return sbjson4_token_error;
case '"':
*token = (char *)(bytes + string_start);
*length = index - string_start;
index++;
return tok;
case '\\':
tok = sbjson4_token_encoded;
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
if (bytes[index] == 'u') {
index++;
if (![self haveRemainingCharacters:4])
return sbjson4_token_eof;
unichar hi;
if (![self decodeHexQuad:&hi]) {
[self setError:@"Invalid hex quad"];
return sbjson4_token_error;
}
if (SBStringIsSurrogateHighCharacter(hi)) {
if (![self haveRemainingCharacters:6])
return sbjson4_token_eof;
unichar lo;
if (bytes[index++] != '\\' || bytes[index++] != 'u' || ![self decodeHexQuad:&lo]) {
[self setError:@"Missing low character in surrogate pair"];
return sbjson4_token_error;
}
if (!SBStringIsSurrogateLowCharacter(lo)) {
[self setError:@"Invalid low character in surrogate pair"];
return sbjson4_token_error;
}
} else if (SBStringIsIllegalSurrogateHighCharacter(hi)) {
[self setError:@"Invalid high character in surrogate pair"];
return sbjson4_token_error;
}
} else {
switch (bytes[index]) {
case '\\':
case '/':
case '"':
case 'b':
case 'n':
case 'r':
case 't':
case 'f':
index++;
break;
default:
[self setError:[NSString stringWithFormat:@"Illegal escape character [%x]", bytes[index]]];
return sbjson4_token_error;
}
}
break;
default:
index++;
break;
}
}
@throw @"FUT FUT FUT";
}
- (sbjson4_token_t)getNumberToken:(char **)token length:(NSUInteger *)length {
NSUInteger num_start = index;
if (bytes[index] == '-') {
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
}
sbjson4_token_t tok = sbjson4_token_integer;
if (bytes[index] == '0') {
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
if (isdigit(bytes[index])) {
[self setError:@"Leading zero is illegal in number"];
return sbjson4_token_error;
}
}
while (isdigit(bytes[index])) {
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
}
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
if (bytes[index] == '.') {
index++;
tok = sbjson4_token_real;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
NSUInteger frac_start = index;
while (isdigit(bytes[index])) {
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
}
if (frac_start == index) {
[self setError:@"No digits after decimal point"];
return sbjson4_token_error;
}
}
if (bytes[index] == 'e' || bytes[index] == 'E') {
index++;
tok = sbjson4_token_real;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
if (bytes[index] == '-' || bytes[index] == '+') {
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
}
NSUInteger exp_start = index;
while (isdigit(bytes[index])) {
index++;
if (![self haveOneMoreCharacter])
return sbjson4_token_eof;
}
if (exp_start == index) {
[self setError:@"No digits in exponent"];
return sbjson4_token_error;
}
}
if (num_start + 1 == index && bytes[num_start] == '-') {
[self setError:@"No digits after initial minus"];
return sbjson4_token_error;
}
*token = (char *)(bytes + num_start);
*length = index - num_start;
return tok;
}
- (sbjson4_token_t)getToken:(char **)token length:(NSUInteger *)length {
[self skipWhitespace];
NSUInteger copyOfIndex = index;
unichar ch;
if (![self getUnichar:&ch])
return sbjson4_token_eof;
sbjson4_token_t tok;
switch (ch) {
case '{': {
index++;
tok = sbjson4_token_object_open;
break;
}
case '}': {
index++;
tok = sbjson4_token_object_close;
break;
}
case '[': {
index++;
tok = sbjson4_token_array_open;
break;
}
case ']': {
index++;
tok = sbjson4_token_array_close;
break;
}
case 't': {
tok = [self match:"true" retval:sbjson4_token_bool token:token length:length];
break;
}
case 'f': {
tok = [self match:"false" retval:sbjson4_token_bool token:token length:length];
break;
}
case 'n': {
tok = [self match:"null" retval:sbjson4_token_null token:token length:length];
break;
}
case ',': {
index++;
tok = sbjson4_token_value_sep;
break;
}
case ':': {
index++;
tok = sbjson4_token_entry_sep;
break;
}
case '"': {
tok = [self getStringToken:token length:length];
break;
}
case '-':
case '0' ... '9': {
tok = [self getNumberToken:token length:length];
break;
}
case '+': {
self.error = @"Leading + is illegal in number";
tok = sbjson4_token_error;
break;
}
default: {
self.error = [NSString stringWithFormat:@"Illegal start of token [%c]", ch];
tok = sbjson4_token_error;
break;
}
}
if (tok == sbjson4_token_eof) {
// We ran out of bytes before we could finish parsing the current token.
// Back up to the start & wait for more data.
index = copyOfIndex;
}
return tok;
}
@end
+210
View File
@@ -0,0 +1,210 @@
/*
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)
/**
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:
- (id)proxyForJson {
return [NSDictionary dictionaryWithObjectsAndKeys:
name, @"name",
phone, @"phone",
email, @"email",
nil];
}
*/
- (id)proxyForJson;
@end
@class SBJson4StreamWriter;
@protocol SBJson4StreamWriterDelegate
- (void)writer:(SBJson4StreamWriter *)writer appendBytes:(const void *)bytes length:(NSUInteger)length;
@end
@class SBJson4StreamWriterState;
/**
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.
Objective-C types are mapped to JSON types in the following way:
- NSNull -> null
- NSString -> string
- NSArray -> array
- NSDictionary -> object
- NSNumber's -initWithBool:YES -> true
- NSNumber's -initWithBool:NO -> false
- NSNumber -> number
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.
@warning: 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.*
*/
@interface SBJson4StreamWriter : NSObject {
NSMutableDictionary *cache;
}
@property (nonatomic, weak) SBJson4StreamWriterState *state; // Internal
@property (nonatomic, readonly, strong) NSMutableArray *stateStack; // Internal
/**
delegate to receive JSON output
Delegate that will receive messages with output.
*/
@property (nonatomic, weak) id<SBJson4StreamWriterDelegate> delegate;
/**
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(nonatomic) NSUInteger maxDepth;
/**
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(nonatomic) BOOL humanReadable;
/**
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(nonatomic) BOOL sortKeys;
/**
An optional comparator to be used if sortKeys is YES.
If this is nil, sorting will be done via @selector(compare:).
*/
@property (nonatomic, copy) NSComparator sortKeysComparator;
/// Contains the error description after an error has occured.
@property (nonatomic, 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 SBJson4StreamWriter (Private)
- (BOOL)writeValue:(id)v;
- (void)appendBytes:(const void *)bytes length:(NSUInteger)length;
@end
+357
View File
@@ -0,0 +1,357 @@
/*
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.
*/
#if !__has_feature(objc_arc)
#error "This source file must be compiled with ARC enabled!"
#endif
#import "SBJson4StreamWriter.h"
#import "SBJson4StreamWriterState.h"
static NSNumber *kTrue;
static NSNumber *kFalse;
static NSNumber *kPositiveInfinity;
static NSNumber *kNegativeInfinity;
@implementation SBJson4StreamWriter
+ (void)initialize {
kPositiveInfinity = [NSNumber numberWithDouble:+HUGE_VAL];
kNegativeInfinity = [NSNumber numberWithDouble:-HUGE_VAL];
kTrue = [NSNumber numberWithBool:YES];
kFalse = [NSNumber numberWithBool:NO];
}
#pragma mark Housekeeping
- (id)init {
self = [super init];
if (self) {
_maxDepth = 32u;
_stateStack = [[NSMutableArray alloc] initWithCapacity:_maxDepth];
_state = [SBJson4StreamWriterStateStart sharedInstance];
cache = [[NSMutableDictionary alloc] initWithCapacity:32];
}
return self;
}
#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) {
if (_sortKeysComparator) {
keys = [keys sortedArrayWithOptions:NSSortStable usingComparator:_sortKeysComparator];
}
else{
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 = [SBJson4StreamWriterStateObjectStart 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;
SBJson4StreamWriterState *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 = [SBJson4StreamWriterStateArrayStart 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;
SBJson4StreamWriterState *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 = [cache 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];
[cache setObject:buf forKey:string];
}
[_delegate writer:self appendBytes:[buf bytes] length:[buf length]];
[_state transitionState:self];
return YES;
}
- (BOOL)writeNumber:(NSNumber*)number {
if (number == kTrue || number == kFalse)
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 ([kPositiveInfinity isEqualToNumber:number]) {
self.error = @"+Infinity is not a valid number in JSON";
return NO;
} else if ([kNegativeInfinity isEqualToNumber:number]) {
self.error = @"-Infinity is not a valid number in JSON";
return NO;
} else if (isnan([number doubleValue])) {
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: {
len = snprintf(num, sizeof num, "%.17g", [number doubleValue]);
break;
}
}
[_delegate writer:self appendBytes:num length: len];
[_state transitionState:self];
return YES;
}
@end
+69
View File
@@ -0,0 +1,69 @@
/*
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 SBJson4StreamWriter;
@interface SBJson4StreamWriterState : NSObject
+ (id)sharedInstance;
- (BOOL)isInvalidState:(SBJson4StreamWriter *)writer;
- (void)appendSeparator:(SBJson4StreamWriter *)writer;
- (BOOL)expectingKey:(SBJson4StreamWriter *)writer;
- (void)transitionState:(SBJson4StreamWriter *)writer;
- (void)appendWhitespace:(SBJson4StreamWriter *)writer;
@end
@interface SBJson4StreamWriterStateObjectStart : SBJson4StreamWriterState
@end
@interface SBJson4StreamWriterStateObjectKey : SBJson4StreamWriterStateObjectStart
@end
@interface SBJson4StreamWriterStateObjectValue : SBJson4StreamWriterState
@end
@interface SBJson4StreamWriterStateArrayStart : SBJson4StreamWriterState
@end
@interface SBJson4StreamWriterStateArrayValue : SBJson4StreamWriterState
@end
@interface SBJson4StreamWriterStateStart : SBJson4StreamWriterState
@end
@interface SBJson4StreamWriterStateComplete : SBJson4StreamWriterState
@end
@interface SBJson4StreamWriterStateError : SBJson4StreamWriterState
@end
+147
View File
@@ -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.
*/
#if !__has_feature(objc_arc)
#error "This source file must be compiled with ARC enabled!"
#endif
#import "SBJson4StreamWriterState.h"
#import "SBJson4StreamWriter.h"
#define SINGLETON \
+ (id)sharedInstance { \
static id state = nil; \
if (!state) { \
@synchronized(self) { \
if (!state) state = [[self alloc] init]; \
} \
} \
return state; \
}
@implementation SBJson4StreamWriterState
+ (id)sharedInstance { return nil; }
- (BOOL)isInvalidState:(SBJson4StreamWriter *)writer { return NO; }
- (void)appendSeparator:(SBJson4StreamWriter *)writer {}
- (BOOL)expectingKey:(SBJson4StreamWriter *)writer { return NO; }
- (void)transitionState:(SBJson4StreamWriter *)writer {}
- (void)appendWhitespace:(SBJson4StreamWriter *)writer {
[writer appendBytes:"\n" length:1];
for (NSUInteger i = 0; i < writer.stateStack.count; i++)
[writer appendBytes:" " length:2];
}
@end
@implementation SBJson4StreamWriterStateObjectStart
SINGLETON
- (void)transitionState:(SBJson4StreamWriter *)writer {
writer.state = [SBJson4StreamWriterStateObjectValue sharedInstance];
}
- (BOOL)expectingKey:(SBJson4StreamWriter *)writer {
writer.error = @"JSON object key must be string";
return YES;
}
@end
@implementation SBJson4StreamWriterStateObjectKey
SINGLETON
- (void)appendSeparator:(SBJson4StreamWriter *)writer {
[writer appendBytes:"," length:1];
}
@end
@implementation SBJson4StreamWriterStateObjectValue
SINGLETON
- (void)appendSeparator:(SBJson4StreamWriter *)writer {
[writer appendBytes:":" length:1];
}
- (void)transitionState:(SBJson4StreamWriter *)writer {
writer.state = [SBJson4StreamWriterStateObjectKey sharedInstance];
}
- (void)appendWhitespace:(SBJson4StreamWriter *)writer {
[writer appendBytes:" " length:1];
}
@end
@implementation SBJson4StreamWriterStateArrayStart
SINGLETON
- (void)transitionState:(SBJson4StreamWriter *)writer {
writer.state = [SBJson4StreamWriterStateArrayValue sharedInstance];
}
@end
@implementation SBJson4StreamWriterStateArrayValue
SINGLETON
- (void)appendSeparator:(SBJson4StreamWriter *)writer {
[writer appendBytes:"," length:1];
}
@end
@implementation SBJson4StreamWriterStateStart
SINGLETON
- (void)transitionState:(SBJson4StreamWriter *)writer {
writer.state = [SBJson4StreamWriterStateComplete sharedInstance];
}
- (void)appendSeparator:(SBJson4StreamWriter *)writer {
}
@end
@implementation SBJson4StreamWriterStateComplete
SINGLETON
- (BOOL)isInvalidState:(SBJson4StreamWriter *)writer {
writer.error = @"Stream is closed";
return YES;
}
@end
@implementation SBJson4StreamWriterStateError
SINGLETON
@end
+103
View File
@@ -0,0 +1,103 @@
/*
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>
/**
The JSON writer class.
This uses SBJson4StreamWriter internally.
*/
@interface SBJson4Writer : NSObject
/**
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(nonatomic) NSUInteger maxDepth;
/**
Return an error trace, or nil if there was no errors.
Note that 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.
*/
@property (nonatomic, readonly, copy) NSString *error;
/**
Whether we are generating human-readable (multiline) JSON.
Set whether or not to generate human-readable JSON. The default is NO, which produces
JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable
JSON with linebreaks after each array value and dictionary key/value pair, indented two
spaces per nesting level.
*/
@property(nonatomic) BOOL humanReadable;
/**
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(nonatomic) BOOL sortKeys;
/**
An optional comparator to be used if sortKeys is YES.
If this is nil, sorting will be done via @selector(compare:).
*/
@property (nonatomic, copy) NSComparator sortKeysComparator;
/**
Generates string with JSON representation for the given object.
Returns a string containing JSON representation of the passed in value, or nil on error.
If nil is returned and error is not NULL, *error can be interrogated to find the cause of the error.
@param value any instance that can be represented as JSON text.
*/
- (NSString*)stringWithObject:(id)value;
/**
Generates JSON representation for the given object.
Returns an NSData object containing JSON represented as UTF8 text, or nil on error.
@param value any instance that can be represented as JSON text.
*/
- (NSData*)dataWithObject:(id)value;
@end
+102
View File
@@ -0,0 +1,102 @@
/*
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.
*/
#if !__has_feature(objc_arc)
#error "This source file must be compiled with ARC enabled!"
#endif
#import "SBJson4Writer.h"
#import "SBJson4StreamWriter.h"
@interface SBJson4Writer () < SBJson4StreamWriterDelegate >
@property (nonatomic, copy) NSString *error;
@property (nonatomic, strong) NSMutableData *acc;
@end
@implementation SBJson4Writer
- (id)init {
self = [super init];
if (self) {
self.maxDepth = 32u;
}
return self;
}
- (NSString*)stringWithObject:(id)value {
NSData *data = [self dataWithObject:value];
if (data)
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return nil;
}
- (NSData*)dataWithObject:(id)object {
self.error = nil;
self.acc = [[NSMutableData alloc] initWithCapacity:8096u];
SBJson4StreamWriter *streamWriter = [[SBJson4StreamWriter alloc] init];
streamWriter.sortKeys = self.sortKeys;
streamWriter.maxDepth = self.maxDepth;
streamWriter.sortKeysComparator = self.sortKeysComparator;
streamWriter.humanReadable = self.humanReadable;
streamWriter.delegate = self;
BOOL ok = NO;
if ([object isKindOfClass:[NSDictionary class]])
ok = [streamWriter writeObject:object];
else if ([object isKindOfClass:[NSArray class]])
ok = [streamWriter writeArray:object];
else if ([object respondsToSelector:@selector(proxyForJson)])
return [self dataWithObject:[object proxyForJson]];
else {
self.error = @"Not valid type for JSON";
return nil;
}
if (ok)
return self.acc;
self.error = streamWriter.error;
return nil;
}
#pragma mark SBJson4StreamWriterDelegate
- (void)writer:(SBJson4StreamWriter *)writer appendBytes:(const void *)bytes length:(NSUInteger)length {
[self.acc appendBytes:bytes length:length];
}
@end