542 lines
17 KiB
Objective-C
542 lines
17 KiB
Objective-C
//
|
|
// MGMJSON.m
|
|
// MGMUsers
|
|
//
|
|
// Created by Mr. Gecko on 7/31/10.
|
|
// Copyright (c) 2014 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
#import "MGMJSON.h"
|
|
|
|
@implementation NSString (MGMJSON)
|
|
- (id)parseJSON {
|
|
MGMJSON *parser = [[MGMJSON alloc] initWithString:self];
|
|
id object = [parser parse];
|
|
[parser release];
|
|
return object;
|
|
}
|
|
- (NSString *)JSONValue {
|
|
MGMJSON *writer = [[MGMJSON alloc] init];
|
|
NSString *value = [writer convert:self];
|
|
[writer release];
|
|
return value;
|
|
}
|
|
@end
|
|
@implementation NSData (MGMJSON)
|
|
- (id)parseJSON {
|
|
NSString *string = [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
|
|
MGMJSON *parser = [[MGMJSON alloc] initWithString:string];
|
|
[string release];
|
|
id object = [parser parse];
|
|
[parser release];
|
|
return object;
|
|
}
|
|
@end
|
|
@implementation NSNumber (MGMJSON)
|
|
- (NSString *)JSONValue {
|
|
MGMJSON *writer = [[MGMJSON alloc] init];
|
|
NSString *value = [writer convert:self];
|
|
[writer release];
|
|
return value;
|
|
}
|
|
@end
|
|
@implementation NSNull (MGMJSON)
|
|
- (NSString *)JSONValue {
|
|
MGMJSON *writer = [[MGMJSON alloc] init];
|
|
NSString *value = [writer convert:self];
|
|
[writer release];
|
|
return value;
|
|
}
|
|
@end
|
|
@implementation NSDictionary (MGMJSON)
|
|
- (NSString *)JSONValue {
|
|
MGMJSON *writer = [[MGMJSON alloc] init];
|
|
NSString *value = [writer convert:self];
|
|
[writer release];
|
|
return value;
|
|
}
|
|
@end
|
|
@implementation NSArray (MGMJSON)
|
|
- (NSString *)JSONValue {
|
|
MGMJSON *writer = [[MGMJSON alloc] init];
|
|
NSString *value = [writer convert:self];
|
|
[writer release];
|
|
return value;
|
|
}
|
|
@end
|
|
|
|
|
|
@implementation MGMJSON
|
|
- (id)init {
|
|
if ((self = [super init])) {
|
|
escapeSet = [[NSMutableCharacterSet characterSetWithRange:NSMakeRange(0,32)] retain];
|
|
[escapeSet addCharactersInString:@"\"\\"];
|
|
}
|
|
return self;
|
|
}
|
|
- (id)initWithString:(NSString *)theString {
|
|
if ((self = [super init])) {
|
|
JSONString = [theString retain];
|
|
length = [JSONString length];
|
|
}
|
|
return self;
|
|
}
|
|
- (void)dealloc {
|
|
[escapeSet release];
|
|
[JSONString release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id)parse {
|
|
position = 0;
|
|
if ([JSONString isEqual:@""])
|
|
return nil;
|
|
return [self parseForObject];
|
|
}
|
|
- (void)skipWhitespace {
|
|
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
|
while (position<length && [set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
position++;
|
|
}
|
|
}
|
|
- (void)skipDigits {
|
|
NSCharacterSet *set = [NSCharacterSet decimalDigitCharacterSet];
|
|
while (position<length && [set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
position++;
|
|
}
|
|
}
|
|
- (id)parseForObject {
|
|
[self skipWhitespace];
|
|
switch ([JSONString characterAtIndex:position]) {
|
|
case '{':
|
|
return [self parseForDictionary];
|
|
break;
|
|
case '[':
|
|
return [self parseForArray];
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
return [self parseForString];
|
|
break;
|
|
case 'T':
|
|
case 't':
|
|
case 'Y':
|
|
case 'y':
|
|
return [self parseForYES];
|
|
break;
|
|
case 'F':
|
|
case 'f':
|
|
case 'N':
|
|
case 'n':
|
|
return [self parseForNONULL];
|
|
break;
|
|
case '-':
|
|
case '0'...'9':
|
|
return [self parseForNumber];
|
|
break;
|
|
case 0x0:
|
|
NSLog(@"JSON: Unexpected end of string.");
|
|
break;
|
|
default:
|
|
NSLog(@"JSON: Unknown character %c", [JSONString characterAtIndex:position]);
|
|
break;
|
|
}
|
|
return nil;
|
|
}
|
|
- (NSDictionary *)parseForDictionary {
|
|
position++;
|
|
[self skipWhitespace];
|
|
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
|
|
while (position<length) {
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
if ([JSONString characterAtIndex:position]=='}') {
|
|
[pool drain];
|
|
return dictionary;
|
|
}
|
|
|
|
NSString *key = nil;
|
|
if ([JSONString characterAtIndex:position]=='"' || [JSONString characterAtIndex:position]=='\'') {
|
|
key = [self parseForString];
|
|
}
|
|
if (key==nil) {
|
|
NSLog(@"JSON: No key found for Dictionary.");
|
|
[pool drain];
|
|
return nil;
|
|
}
|
|
position++;
|
|
|
|
[self skipWhitespace];
|
|
if ([JSONString characterAtIndex:position]!=':') {
|
|
NSLog(@"JSON: Expected \":\" in Dictionary.");
|
|
[pool drain];
|
|
return nil;
|
|
}
|
|
position++;
|
|
|
|
id object = [self parseForObject];
|
|
if (object!=nil) {
|
|
[dictionary setObject:object forKey:key];
|
|
} else {
|
|
NSLog(@"JSON: Found no object for Dictionary.");
|
|
[pool drain];
|
|
return nil;
|
|
}
|
|
position++;
|
|
|
|
[self skipWhitespace];
|
|
if ([JSONString characterAtIndex:position]==',') {
|
|
position++;
|
|
[self skipWhitespace];
|
|
if ([JSONString characterAtIndex:position]=='}') {
|
|
NSLog(@"JSON: Unexpected end of Dictionary.");
|
|
[pool drain];
|
|
return dictionary;
|
|
}
|
|
}
|
|
[pool drain];
|
|
}
|
|
NSLog(@"JSON: Unexpected end of input for Dictionary.");
|
|
return dictionary;
|
|
}
|
|
- (NSArray *)parseForArray {
|
|
position++;
|
|
[self skipWhitespace];
|
|
NSMutableArray *array = [NSMutableArray array];
|
|
while (position<length) {
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
if ([JSONString characterAtIndex:position]==']') {
|
|
[pool drain];
|
|
return array;
|
|
}
|
|
|
|
id object = [self parseForObject];
|
|
if (object!=nil) {
|
|
[array addObject:object];
|
|
} else {
|
|
NSLog(@"JSON: Found no object for Array.");
|
|
[pool drain];
|
|
return nil;
|
|
}
|
|
position++;
|
|
|
|
[self skipWhitespace];
|
|
if ([JSONString characterAtIndex:position]==',') {
|
|
position++;
|
|
[self skipWhitespace];
|
|
if ([JSONString characterAtIndex:position]==']') {
|
|
NSLog(@"JSON: Unexpected end of Array.");
|
|
[pool drain];
|
|
return array;
|
|
}
|
|
}
|
|
[pool drain];
|
|
}
|
|
NSLog(@"JSON: Unexpected end of input for Array.");
|
|
return array;
|
|
}
|
|
- (NSString *)parseForString {
|
|
unichar quote = [JSONString characterAtIndex:position];
|
|
position++;
|
|
NSMutableString *string = [NSMutableString string];
|
|
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithFormat:@"%C\\", quote]];
|
|
do {
|
|
NSAutoreleasePool *pool = [NSAutoreleasePool new];
|
|
NSString *currentString = [[[JSONString substringFromIndex:position] copy] autorelease];
|
|
NSRange range = [currentString rangeOfCharacterFromSet:set];
|
|
if (range.location==NSNotFound) {
|
|
NSLog(@"JSON: Range not found for String.");
|
|
[pool drain];
|
|
return nil;
|
|
}
|
|
[string appendString:[currentString substringWithRange:NSMakeRange(0, range.location)]];
|
|
position += range.location;
|
|
if ([JSONString characterAtIndex:position]==quote) {
|
|
[pool drain];
|
|
return string;
|
|
} else if ([JSONString characterAtIndex:position]=='\\') {
|
|
position++;
|
|
unichar character = [JSONString characterAtIndex:position];
|
|
switch (character) {
|
|
case '\\':
|
|
case '/':
|
|
case '"':
|
|
case '\'':
|
|
break;
|
|
|
|
case 'U':
|
|
case 'u':
|
|
position++;
|
|
character = [self parseForUnicodeChar];
|
|
break;
|
|
|
|
case 'b':
|
|
character = '\b';
|
|
break;
|
|
case 'f':
|
|
character = '\f';
|
|
break;
|
|
case 'n':
|
|
character = '\n';
|
|
break;
|
|
case 'r':
|
|
character = '\r';
|
|
break;
|
|
case 't':
|
|
character = '\t';
|
|
break;
|
|
default:
|
|
NSLog(@"JSON: Illegal escape \"0x%x\"", character);
|
|
[pool drain];
|
|
return nil;
|
|
break;
|
|
}
|
|
if (character==0x0) {
|
|
NSLog(@"JSON: Received NULL when expecting character.");
|
|
[pool drain];
|
|
return nil;
|
|
}
|
|
CFStringAppendCharacters((CFMutableStringRef)string, &character, 1);
|
|
position++;
|
|
}
|
|
[pool drain];
|
|
} while (position<length);
|
|
NSLog(@"JSON: Unexpected end of input for String.");
|
|
return [[string copy] autorelease];
|
|
}
|
|
- (unichar)parseForUnicodeChar {
|
|
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"0123456789aAbBcCdDeEfF"];
|
|
unsigned long toPosition = position + 4;
|
|
if (toPosition>length) {
|
|
NSLog(@"JSON: Unexpected end of input.");
|
|
return 0x0;
|
|
}
|
|
int len = 0;
|
|
NSMutableString *hexValue = [NSMutableString string];
|
|
[hexValue appendString:@"0x"];
|
|
while (position<toPosition && [set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
[hexValue appendFormat:@"%c", [JSONString characterAtIndex:position]];
|
|
len++;
|
|
position++;
|
|
}
|
|
if (len!=4) {
|
|
NSLog(@"JSON: Unicode Character invaild.");
|
|
return 0x0;
|
|
}
|
|
position--;
|
|
NSScanner *scanner = [NSScanner scannerWithString: hexValue];
|
|
unsigned int returnValue;
|
|
if ([scanner scanHexInt:&returnValue]) {
|
|
return returnValue;
|
|
}
|
|
NSLog(@"JSON: Failed to scan hex.");
|
|
return 0x0;
|
|
}
|
|
- (NSNumber *)parseForYES {
|
|
if ([JSONString characterAtIndex:position]=='T' || [JSONString characterAtIndex:position]=='t') {
|
|
if ([[[JSONString substringWithRange:NSMakeRange(position, 4)] lowercaseString] isEqual:@"true"]) {
|
|
position += 3;
|
|
return [NSNumber numberWithBool:YES];
|
|
}
|
|
} else if ([JSONString characterAtIndex:position]=='Y' || [JSONString characterAtIndex:position]=='y') {
|
|
if ([[[JSONString substringWithRange:NSMakeRange(position, 3)] lowercaseString] isEqual:@"yes"]) {
|
|
position += 2;
|
|
return [NSNumber numberWithBool:YES];
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
- (id)parseForNONULL {
|
|
if ([JSONString characterAtIndex:position]=='F' || [JSONString characterAtIndex:position]=='f') {
|
|
if ([[[JSONString substringWithRange:NSMakeRange(position, 5)] lowercaseString] isEqual:@"false"]) {
|
|
position += 4;
|
|
return [NSNumber numberWithBool:NO];
|
|
}
|
|
} else if ([JSONString characterAtIndex:position]=='N' || [JSONString characterAtIndex:position]=='n') {
|
|
if ([[[JSONString substringWithRange:NSMakeRange(position, 2)] lowercaseString] isEqual:@"no"]) {
|
|
position += 1;
|
|
return [NSNumber numberWithBool:NO];
|
|
} else if ([[[JSONString substringWithRange:NSMakeRange(position, 4)] lowercaseString] isEqual:@"null"]) {
|
|
position += 3;
|
|
return [NSNull null];
|
|
} else if ([[[JSONString substringWithRange:NSMakeRange(position, 3)] lowercaseString] isEqual:@"nil"]) {
|
|
position += 2;
|
|
return [NSNull null];
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
- (NSNumber *)parseForNumber {
|
|
unsigned long start = position;
|
|
NSCharacterSet *set = [NSCharacterSet decimalDigitCharacterSet];
|
|
|
|
if ([JSONString characterAtIndex:position]=='-') {
|
|
position++;
|
|
if (position<length && ![set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
NSLog(@"JSON: Expected digit after minus.");
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
if ([JSONString characterAtIndex:position]=='0') {
|
|
position++;
|
|
if (position<length && [set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
NSLog(@"JSON: Leading zero found.");
|
|
return nil;
|
|
}
|
|
} else {
|
|
[self skipDigits];
|
|
}
|
|
|
|
if (position<length && [JSONString characterAtIndex:position]=='.') {
|
|
position++;
|
|
if (position<length && ![set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
NSLog(@"JSON: No digits found after decimal point.");
|
|
return nil;
|
|
}
|
|
[self skipDigits];
|
|
}
|
|
|
|
if (position<length && ([JSONString characterAtIndex:position]=='E' || [JSONString characterAtIndex:position]=='e')) {
|
|
position++;
|
|
if (position<length && ([JSONString characterAtIndex:position]=='-' || [JSONString characterAtIndex:position]=='+'))
|
|
position++;
|
|
|
|
if (position<length && ![set characterIsMember:[JSONString characterAtIndex:position]]) {
|
|
NSLog(@"JSON: No digits found after exponent.");
|
|
return nil;
|
|
}
|
|
[self skipDigits];
|
|
}
|
|
|
|
NSString *numberString = [[[JSONString substringWithRange:NSMakeRange(start, position-start)] copy] autorelease];
|
|
position--;
|
|
if (numberString!=nil) {
|
|
NSNumber *number = [NSDecimalNumber decimalNumberWithString:numberString];
|
|
if (number!=nil)
|
|
return number;
|
|
}
|
|
NSLog(@"JSON: Couldn't create number.");
|
|
return nil;
|
|
}
|
|
|
|
- (NSString *)convert:(id)theObject {
|
|
if ([theObject isKindOfClass:[NSString class]]) {
|
|
return [self writeString:theObject];
|
|
} else if ([NSStringFromClass([theObject class]) isEqual:@"NSCFBoolean"]) {
|
|
return [self writeBool:theObject];
|
|
} else if ([theObject isKindOfClass:[NSNumber class]]) {
|
|
return [self writeNumber:theObject];
|
|
} else if (theObject==nil || [theObject isKindOfClass:[NSNull class]]) {
|
|
return [self writeNull:theObject];
|
|
} else if ([theObject isKindOfClass:[NSDictionary class]]) {
|
|
return [self writeDictionary:theObject];
|
|
} else if ([theObject isKindOfClass:[NSArray class]]) {
|
|
return [self writeArray:theObject];
|
|
}
|
|
NSLog(@"JSON: Invalid calss for JSON %@, returning description.", NSStringFromClass([theObject class]));
|
|
return [self writeString:[theObject description]];
|
|
}
|
|
- (NSString *)writeString:(NSString *)theString {
|
|
NSRange range = [theString rangeOfCharacterFromSet:escapeSet];
|
|
NSMutableString *string = [NSMutableString string];
|
|
[string appendString:@"\""];
|
|
if (range.location==NSNotFound) {
|
|
[string appendString:theString];
|
|
} else {
|
|
unsigned long len = [theString length];
|
|
for (unsigned long i=0; i<len; i++) {
|
|
unichar character = [theString characterAtIndex:i];
|
|
switch (character) {
|
|
case '"':
|
|
[string appendString:@"\\\""];
|
|
break;
|
|
case '\\':
|
|
[string appendString:@"\\\\"];
|
|
break;
|
|
case '\b':
|
|
[string appendString:@"\\b"];
|
|
break;
|
|
case '\f':
|
|
[string appendString:@"\\f"];
|
|
break;
|
|
case '\n':
|
|
[string appendString:@"\\n"];
|
|
break;
|
|
case '\r':
|
|
[string appendString:@"\\r"];
|
|
break;
|
|
case '\t':
|
|
[string appendString:@"\\t"];
|
|
break;
|
|
default:
|
|
if (character<0x20) {
|
|
[string appendFormat:@"\\u%04x", character];
|
|
} else {
|
|
CFStringAppendCharacters((CFMutableStringRef)string, &character, 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
[string appendString:@"\""];
|
|
return string;
|
|
}
|
|
- (NSString *)writeNumber:(NSNumber *)theNumber {
|
|
return [theNumber stringValue];
|
|
}
|
|
- (NSString *)writeBool:(NSNumber *)theNumber {
|
|
return [theNumber boolValue] ? @"true" : @"false";
|
|
}
|
|
- (NSString *)writeNull:(NSNull *)theNull {
|
|
return @"null";
|
|
}
|
|
- (NSString *)writeDictionary:(NSDictionary *)theDictionary {
|
|
NSMutableString *string = [NSMutableString string];
|
|
[string appendString:@"{"];
|
|
NSArray *keys = [theDictionary allKeys];
|
|
for (int i=0; i<[keys count]; i++) {
|
|
if (i!=0) {
|
|
[string appendString:@", "];
|
|
}
|
|
NSString *value = [self convert:[theDictionary objectForKey:[keys objectAtIndex:i]]];
|
|
if (value!=nil) {
|
|
[string appendFormat:@"%@: ", [self writeString:[keys objectAtIndex:i]]];
|
|
[string appendString:value];
|
|
}
|
|
}
|
|
[string appendString:@"}"];
|
|
return string;
|
|
}
|
|
- (NSString *)writeArray:(NSArray *)theArray {
|
|
NSMutableString *string = [NSMutableString string];
|
|
[string appendString:@"["];
|
|
for (int i=0; i<[theArray count]; i++) {
|
|
if (i!=0) {
|
|
[string appendString:@", "];
|
|
}
|
|
NSString *value = [self convert:[theArray objectAtIndex:i]];
|
|
if (value!=nil)
|
|
[string appendString:value];
|
|
}
|
|
[string appendString:@"]"];
|
|
return string;
|
|
}
|
|
@end |