Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Last active December 18, 2015 02:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kristopherjohnson/5715018 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/5715018 to your computer and use it in GitHub Desktop.
Unescape a JSON string.
#import <SenTestingKit/SenTestingKit.h>
#import "NSString+KDJ_stringWthJSONString.h"
@interface KDJ_stringWthJSONString_Tests : SenTestCase
@end
@implementation KDJ_stringWthJSONString_Tests
- (void)test_stringFromJSONString {
STAssertEqualObjects(@"", [NSString stringWithJSONString:@"\"\""], @"empty string");
STAssertEqualObjects(@"foo", [NSString stringWithJSONString:@"\"foo\""], @"simple string with no escaped characters");
STAssertEqualObjects(@"\"", [NSString stringWithJSONString:@"\"\\\"\""], @"escaped double-quote character");
STAssertEqualObjects(@"\\", [NSString stringWithJSONString:@"\"\\\\\""], @"escaped backslash");
STAssertEqualObjects(@"/", [NSString stringWithJSONString:@"\"\\/\""], @"escaped forward slash");
STAssertEqualObjects(@"\b", [NSString stringWithJSONString:@"\"\\b\""], @"escaped backspace");
STAssertEqualObjects(@"\f", [NSString stringWithJSONString:@"\"\\f\""], @"escaped form feed");
STAssertEqualObjects(@"\n", [NSString stringWithJSONString:@"\"\\n\""], @"escaped line feed");
STAssertEqualObjects(@"\r", [NSString stringWithJSONString:@"\"\\r\""], @"escaped carriage return");
STAssertEqualObjects(@"\t", [NSString stringWithJSONString:@"\"\\t\""], @"escaped tab");
STAssertEqualObjects(@" ", [NSString stringWithJSONString:@"\"\\u0020\""], @"escaped Unicode");
STAssertEqualObjects(@"zFoo", [NSString stringWithJSONString:@"\"\\u007aFoo\""], @"string with escaped Unicode");
STAssertEqualObjects(@"She said, \"Hello!\"\n\tThen she said \"Goodbye.\"",
[NSString stringWithJSONString:@"\"She said, \\\"Hello!\\\"\\n\\tThen she said \\\"Goodbye.\\\"\""],
@"Complicated string");
STAssertNil([NSString stringWithJSONString:@""], @"must start and end with double-quote characters");
STAssertNil([NSString stringWithJSONString:@"foo"], @"must start and end with double-quote characters");
STAssertNil([NSString stringWithJSONString:@"\"foo"], @"must start and end with double-quote characters");
STAssertNil([NSString stringWithJSONString:@"foo\""], @"must start and end with double-quote characters");
STAssertNil([NSString stringWithJSONString:@"\"foo\\\""], @"cannot end with backslash");
STAssertNil([NSString stringWithJSONString:@"\"\\u123"], @"four characters must follow \\u");
}
@end
#import <Foundation/Foundation.h>
@interface NSString (KDJ_stringWithJSONString)
// Return string from a JSON-encoded string, or return nil if argument is not a valid JSON string value.
//
// See RFC 4627 section 2.5 for details of JSON string representation.
// The string must begin and end with double-quote (") characters.
+ (NSString *)stringWithJSONString:(NSString *)jsonString;
@end
#import "NSString+KDJ_stringWithJSONString.h"
@implementation NSString (KDJ_stringWithJSONString)
+ (NSString *)stringWithJSONString:(NSString *)jsonString {
NSUInteger len = [jsonString length];
if (len < 2 || [jsonString characterAtIndex:0] != '"' || [jsonString characterAtIndex:(len - 1)] != '"') {
NSLog(@"%s: string does not begin and end with \" characters", __PRETTY_FUNCTION__);
return nil;
}
NSMutableString *result = [[NSMutableString alloc] initWithCapacity:(len-2)];
NSUInteger limit = len - 1;
for (NSUInteger i = 1; i < limit; ++i) {
unichar character = [jsonString characterAtIndex:i];
if (character == '\\') {
// Escape sequence
if (i == limit - 1) {
NSLog(@"%s: JSON string cannot end with single backslash", __PRETTY_FUNCTION__);
return nil;
}
++i;
unichar nextCharacter = [jsonString characterAtIndex:i];
switch (nextCharacter) {
case 'b':
[result appendString:@"\b"];
break;
case 'f':
[result appendString:@"\f"];
break;
case 'n':
[result appendString:@"\n"];
break;
case 'r':
[result appendString:@"\r"];
break;
case 't':
[result appendString:@"\t"];
break;
case 'u':
if ((i + 4) >= limit) {
NSLog(@"%s: insufficient characters remaining after \\u in JSON string", __PRETTY_FUNCTION__);
return nil;
}
{
NSString *hexdigits = [jsonString substringWithRange:NSMakeRange(i + 1, 4)];
i += 4;
NSScanner *scanner = [NSScanner scannerWithString:hexdigits];
unsigned int hexValue = 0;
if (![scanner scanHexInt:&hexValue]) {
NSLog(@"%s: invalid hex digits following \\u", __PRETTY_FUNCTION__);
}
[result appendFormat:@"%C", (unichar)hexValue];
}
break;
default:
[result appendFormat:@"%C", nextCharacter];
break;
}
}
else {
// No escape
[result appendFormat:@"%C", character];
}
}
// Return an immutable copy
return [NSString stringWithString:result];
}
@end
@kristopherjohnson
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment