249 lines
8.1 KiB
Objective-C
249 lines
8.1 KiB
Objective-C
/*
|
|
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 "SBJsonWriter.h"
|
|
|
|
@interface SBJsonWriter ()
|
|
|
|
- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json;
|
|
- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json;
|
|
- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json;
|
|
- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json;
|
|
|
|
- (NSString*)indent;
|
|
|
|
@end
|
|
|
|
@implementation SBJsonWriter
|
|
|
|
static NSMutableCharacterSet *kEscapeChars;
|
|
|
|
+ (void)initialize {
|
|
kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain];
|
|
[kEscapeChars addCharactersInString: @"\"\\"];
|
|
}
|
|
|
|
- (void)setHumanReadable:(BOOL)isHumanReadable {
|
|
humanReadable = isHumanReadable;
|
|
}
|
|
- (BOOL)humanReadable {
|
|
return humanReadable;
|
|
}
|
|
- (void)setSortKeys:(BOOL)willShortKeys {
|
|
sortKeys = willShortKeys;
|
|
}
|
|
- (BOOL)sortKeys {
|
|
return sortKeys;
|
|
}
|
|
|
|
/**
|
|
@deprecated This exists in order to provide fragment support in older APIs in one more version.
|
|
It should be removed in the next major version.
|
|
*/
|
|
- (NSString*)stringWithFragment:(id)value {
|
|
[self clearErrorTrace];
|
|
depth = 0;
|
|
NSMutableString *json = [NSMutableString stringWithCapacity:128];
|
|
|
|
if ([self appendValue:value into:json])
|
|
return json;
|
|
|
|
return nil;
|
|
}
|
|
|
|
|
|
- (NSString*)stringWithObject:(id)value {
|
|
|
|
if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) {
|
|
return [self stringWithFragment:value];
|
|
}
|
|
|
|
if ([value respondsToSelector:@selector(proxyForJson)]) {
|
|
NSString *tmp = [self stringWithObject:[value proxyForJson]];
|
|
if (tmp)
|
|
return tmp;
|
|
}
|
|
|
|
|
|
[self clearErrorTrace];
|
|
[self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"];
|
|
return nil;
|
|
}
|
|
|
|
|
|
- (NSString*)indent {
|
|
return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0];
|
|
}
|
|
|
|
- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json {
|
|
if ([fragment isKindOfClass:[NSDictionary class]]) {
|
|
if (![self appendDictionary:fragment into:json])
|
|
return NO;
|
|
|
|
} else if ([fragment isKindOfClass:[NSArray class]]) {
|
|
if (![self appendArray:fragment into:json])
|
|
return NO;
|
|
|
|
} else if ([fragment isKindOfClass:[NSString class]]) {
|
|
if (![self appendString:fragment into:json])
|
|
return NO;
|
|
|
|
} else if ([fragment isKindOfClass:[NSNumber class]]) {
|
|
if ('c' == *[fragment objCType])
|
|
[json appendString:[fragment boolValue] ? @"true" : @"false"];
|
|
else
|
|
[json appendString:[fragment stringValue]];
|
|
|
|
} else if ([fragment isKindOfClass:[NSNull class]]) {
|
|
[json appendString:@"null"];
|
|
} else if ([fragment respondsToSelector:@selector(proxyForJson)]) {
|
|
[self appendValue:[fragment proxyForJson] into:json];
|
|
|
|
} else {
|
|
[self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]];
|
|
return NO;
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json {
|
|
if (maxDepth && ++depth > maxDepth) {
|
|
[self addErrorWithCode:EDEPTH description: @"Nested too deep"];
|
|
return NO;
|
|
}
|
|
[json appendString:@"["];
|
|
|
|
BOOL addComma = NO;
|
|
for (int i=0; i<[fragment count]; i++) {
|
|
id value = [fragment objectAtIndex:i];
|
|
if (addComma)
|
|
[json appendString:@","];
|
|
else
|
|
addComma = YES;
|
|
|
|
if ([self humanReadable])
|
|
[json appendString:[self indent]];
|
|
|
|
if (![self appendValue:value into:json]) {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
depth--;
|
|
if ([self humanReadable] && [fragment count])
|
|
[json appendString:[self indent]];
|
|
[json appendString:@"]"];
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json {
|
|
if (maxDepth && ++depth > maxDepth) {
|
|
[self addErrorWithCode:EDEPTH description: @"Nested too deep"];
|
|
return NO;
|
|
}
|
|
[json appendString:@"{"];
|
|
|
|
NSString *colon = [self humanReadable] ? @" : " : @":";
|
|
BOOL addComma = NO;
|
|
NSArray *keys = [fragment allKeys];
|
|
if (sortKeys)
|
|
keys = [keys sortedArrayUsingSelector:@selector(compare:)];
|
|
|
|
for (int i=0; i<[keys count]; i++) {
|
|
id value = [keys objectAtIndex:i];
|
|
if (addComma)
|
|
[json appendString:@","];
|
|
else
|
|
addComma = YES;
|
|
|
|
if ([self humanReadable])
|
|
[json appendString:[self indent]];
|
|
|
|
if (![value isKindOfClass:[NSString class]]) {
|
|
[self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"];
|
|
return NO;
|
|
}
|
|
|
|
if (![self appendString:value into:json])
|
|
return NO;
|
|
|
|
[json appendString:colon];
|
|
if (![self appendValue:[fragment objectForKey:value] into:json]) {
|
|
[self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]];
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
depth--;
|
|
if ([self humanReadable] && [fragment count])
|
|
[json appendString:[self indent]];
|
|
[json appendString:@"}"];
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json {
|
|
|
|
[json appendString:@"\""];
|
|
|
|
NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars];
|
|
if ( !esc.length ) {
|
|
// No special chars -- can just add the raw string:
|
|
[json appendString:fragment];
|
|
|
|
} else {
|
|
unsigned int length = [fragment length];
|
|
for (unsigned int i = 0; i < length; i++) {
|
|
unichar uc = [fragment characterAtIndex:i];
|
|
switch (uc) {
|
|
case '"': [json appendString:@"\\\""]; break;
|
|
case '\\': [json appendString:@"\\\\"]; break;
|
|
case '\t': [json appendString:@"\\t"]; break;
|
|
case '\n': [json appendString:@"\\n"]; break;
|
|
case '\r': [json appendString:@"\\r"]; break;
|
|
case '\b': [json appendString:@"\\b"]; break;
|
|
case '\f': [json appendString:@"\\f"]; break;
|
|
default:
|
|
if (uc < 0x20) {
|
|
[json appendFormat:@"\\u%04x", uc];
|
|
} else {
|
|
CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1);
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
[json appendString:@"\""];
|
|
return YES;
|
|
}
|
|
|
|
|
|
@end
|