In the preceding parts of this series, we examined the current state of JSON parsing in Apple environments, particularly within Swift. The results were far from ideal: It’s widely understood that parsing JSON into intermediate Foundation types (like dictionaries, arrays, strings, and numbers) is inefficient, yet it remains the fastest method at our disposal. Creating objects directly from JSON should be slower, yet it’s surprisingly faster. Lastly, we know that transferring values to these objects should be the slowest part, but Swift Coding significantly underperforms.
We’re left with two possibilities: either our understanding is flawed, or something unusual is happening. I lean towards the latter. While investigating the root cause would be intriguing, I prefer a more proactive approach: creating a solution without these issues.
MASON
Let’s dive into the definition of the MPWMASONParser class:
` ``` @class MPWSmallStringTable; @protocol MPWPlistStreaming;
@interface MPWMASONParser : MPWXmlAppleProplistReader { BOOL inDict; BOOL inArray; MPWSmallStringTable *commonStrings; }
@property (nonatomic, strong) id builder;
-(void)setFrequentStrings:(NSArray*)strings;
@end ``` `
This class sends messages defined by the MPWPlistStreaming protocol to its builder property. In essence, it’s a Message-oriented parser for JaSON, mirroring the approach of MAX as the Message-oriented API for XML.
The implementation history is evident in its lineage: MPWMASONParser is a subclass of [MPWXmlAppleProplistReader](https://github.com/mpw/MPWFoundation/blob/master/XML/MPWXmlAppleProplistReader.h), which itself inherits from [MPWMAXParser](https://github.com/mpw/MPWFoundation/blob/master/XML/MPWMAXParser.h). The heart of its functionality lies in a loop that processes JSON syntax and dispatches messages for different elements to the designated builder. This loop resembles those found in straightforward parsers (and likely differs greatly from the SIMD optimizations in simdjson). Upon completion, it returns the output generated by the builder.
` ``` -parsedData:(NSData*)jsonData { [self setData:jsonData]; const char *curptr=[jsonData bytes]; const char *endptr=curptr+[jsonData length]; const char *stringstart=NULL; NSString *curstr=nil; while (curptr < endptr ) { switch (*curptr) { case ‘{’: [_builder beginDictionary]; inDict=YES; inArray=NO; curptr++; break; case ‘}’: [_builder endDictionary]; curptr++; break; case ‘[’: [_builder beginArray]; inDict=NO; inArray=YES; curptr++; break; case ‘]’: [_builder endArray]; curptr++; break; case ‘"’: parsestring( curptr , endptr, &stringstart, &curptr ); curstr = [self makeRetainedJSONStringStart:stringstart length:curptr-stringstart]; curptr++; if ( *curptr == ‘:’ ) { [_builder writeKey:curstr]; curptr++;
} else {
[_builder writeString:curstr];
}
break;
case ',':
curptr++;
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
BOOL isReal=NO;
const char *numstart=curptr;
id number=nil;
if ( *curptr == '-' ) {
curptr++;
}
while ( curptr < endptr && isdigit(*curptr) ) {
curptr++;
}
if ( *curptr == '.' ) {
curptr++;
while ( curptr < endptr && isdigit(*curptr) ) {
curptr++;
}
isReal=YES;
}
if ( curptr < endptr && (*curptr=='e' | *curptr=='E') ) {
curptr++;
while ( curptr < endptr && isdigit(*curptr) ) {
curptr++;
}
isReal=YES;
}
number = isReal ?
[self realElement:numstart length:curptr-numstart] :
[self integerElementAtPtr:numstart length:curptr-numstart];
[_builder writeString:number]; break; } case ’t’: if ( (endptr-curptr) >=4 && !strncmp(curptr, “true”, 4)) { curptr+=4; [_builder pushObject:true_value]; } break; case ‘f’: if ( (endptr-curptr) >=5 && !strncmp(curptr, “false”, 5)) { // return false; curptr+=5; [_builder pushObject:false_value];
} break; case ’n’: if ( (endptr-curptr) >=4 && !strncmp(curptr, “null”, 4)) { [_builder pushObject:[NSNull null]]; curptr+=4; } break; case ’ ‘: case ‘\n’: while (curptr < endptr && isspace(*curptr)) { curptr++; } break;
default: [NSException raise:@“invalidcharacter” format:@“JSON invalid character %x/’%c’ at %td”,*curptr,curptr,curptr-(char)[data bytes]]; break; } } return [_builder result];
}
``` `
It may not handle all edge cases flawlessly, but such refinements are unlikely to significantly affect performance.
Decoupling Property Lists with MPWPlistStreaming
As previously noted, MASON operates on a message-based system, utilizing the MPWPlistStreaming protocol to communicate with its builder. Here’s the protocol definition:
` ``` @protocol MPWPlistStreaming
-(void)beginArray; -(void)endArray; -(void)beginDictionary; -(void)endDictionary; -(void)writeKey:aKey; -(void)writeString:aString; -(void)writeNumber:aNumber; -(void)writeObject:anObject forKey:aKey; -(void)pushContainer:anObject; -(void)pushObject:anObject;
@end
``` `
This approach allows the use of property list structures without directly creating instances. Instead, we send messages that would be sent if a property list were present. This echoes the concept of Protocol Oriented Programming, but applicable outside of Swift.
Furthermore, the same protocol can be employed on the output side, resembling the functionality provided by Standard Object Out.
Putting it to the Test
By default, MPWMASONParser utilizes an instance of MPWPlistBuilder as its builder. True to its name, this builder constructs property lists, similar to NSJSONSerialization.
Let’s run a quick test:
` ``` -(void)decodeMPWDicts:(NSData*)json { MPWMASONParser parser=[MPWMASONParser parser]; NSArray plistResult = [parser parsedData:json]; NSLog(@“MPWMASON %@ with %ld dicts”,[plistResult firstObject],[plistResult count]); }
``` `
The result? A disappointing 0.621 seconds.
Despite our efforts, we fall short of NSJSONSerialization by nearly 50%. Perhaps those Apple engineers are onto something, and we should admit defeat.
However, let’s investigate further before giving up. Time to unleash Instruments!

The results are illuminating: The majority of the processing time is consumed by Apple’s code responsible for building the property list, a necessary step in our process. How does NSJSONSerialization manage to perform the same task more efficiently? While they appear to utilize NSPropertyListSerialization, a closer look reveals they employ specialized CoreFoundation dictionaries optimized for a large number of string keys. Sadly, these dictionaries aren’t publicly accessible, and CoreFoundation’s C-based nature effectively hides them.
So, how can we improve? Stay tuned for the exciting conclusion in next exciting instalment!
TOC
Somewhat Less Lethargic JSON Support for iOS/macOS, Part 1: The Status Quo
Somewhat Less Lethargic JSON Support for iOS/macOS, Part 2: Analysis
Somewhat Less Lethargic JSON Support for iOS/macOS, Part 3: Dematerialization
Equally Lethargic JSON Support for iOS/macOS, Part 4: Our Keys are Small but Legion
Less Lethargic JSON Support for iOS/macOS, Part 5: Cutting out the Middleman
Somewhat Faster JSON Support for iOS/macOS, Part 6: Cutting KVC out of the Loop
Faster JSON Support for iOS/macOS, Part 7: Polishing the Parser
Faster JSON Support for iOS/macOS, Part 8: Dematerialize All the Things!
Beyond Faster JSON Support for iOS/macOS, Part 9: CSV and SQLite
