mirror of
https://github.com/wu736139669/Ash_AWord.git
synced 2026-08-05 05:55:19 +00:00
base
This commit is contained in:
Generated
+35
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user