This framework implements a strict JSON parser and generator in Objective-C.
Objective-C
3,713
1,199 commits
updated May 18, 2026
Chunk-based JSON parsing and generation in Objective-C.
SBJson's number one feature is stream/chunk-based operation. Feed the parser one or more chunks of UTF8-encoded data and it will call a block you provide with each root-level document or array. Or, optionally, for each top-level entry in each root-level array.
With this 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 full document has downloaded. You can also parse massive documents bit by bit so you don't have to keep them all in memory.
SBJson maps JSON types to Objective-C types in the following way:
| JSON Type | Objective-C Type |
|---|---|
| null | NSNull |
| string | NSString |
| array | NSMutableArray |
| object | NSMutableDictionary |
| true | -[NSNumber numberWithBool: YES] |
| false | -[NSNumber numberWithBool: NO] |
| number | NSNumber |
long long or unsigned long long if they fit,
to avoid rounding errors. For all other numbers we use the double
type, with all the potential rounding errors that entails.First define a simple block & an error handler. (These are just minimal examples. You should strive to do something better that makes sense in your application!)
SBJson5ValueBlock block = ^(id v, BOOL *stop) {
BOOL isArray = [v isKindOfClass:[NSArray class]];
NSLog(@"Found: %@", isArray ? @"Array" : @"Object");
};
SBJson5ErrorBlock eh = ^(NSError* err) {
NSLog(@"OOPS: %@", err);
exit(1);
};
Then create a parser and add data to it:
id parser = [SBJson5Parser parserWithBlock:block
errorHandler:eh];
id data = [@"[true," dataWithEncoding:NSUTF8StringEncoding];
[parser parse:data]; // returns SBJson5ParserWaitingForData
// block is not called yet...
// ok, now we add another value and close the array
data = [@"false]" dataWithEncoding:NSUTF8StringEncoding];
[parser parse:data]; // returns SBJson5ParserComplete
// the above -parse: method calls your block before returning.
Alright! Now let's look at something slightly more interesting.
This is useful for something like Twitter's feed, which gives you one JSON document per line. Here is an example of parsing many consequtive JSON documents, where your block will be called once for each document:
id parser = [SBJson5Parser 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 use a multiRootParser. But, all is not lost: if you are parsing a long array you can get the same effect by using an unwrapRootArrayParser:
id parser = [SBJson5Parser unwrapRootArrayParserWithBlock:block
errorHandler:eh];
// Note that this input contains A SINGLE top-level document
id data = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
[parser parse:data];
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.
I've run AFL++ on the sbjson binary for over 24 hours, with no crashes found. (I cannot reproduce the hangs reported when attempting to parse them manually.)
To reproduce, make sure you have Nix installed, then:
git checkout fuzz
nix-shell --command ./fuzz.sh
The shell.nix builds AFL++ 4.34c from source, configured to use
POSIX shared memory (shm_open + mmap) so it runs on macOS without
the System V IPC issues the packaged version has. The fuzz.sh script
builds an instrumented sbjson binary and starts afl-fuzz seeded with
the jsonchecker test data.
Here's the output after about 24 hours of fuzzing:
american fuzzy lop ++4.34c {default} (/tmp/sbjson-afl/sbjson) [explore]
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│ run time : 1 days, 0 hrs, 26 min, 51 sec │ cycles done : 57 │
│ last new find : 0 days, 0 hrs, 30 min, 9 sec │ corpus count : 1089 │
│last saved crash : none seen yet │saved crashes : 0 │
│ last saved hang : none seen yet │ saved hangs : 0 │
├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
│ now processing : 1055.224 (96.9%) │ map density : 8.85% / 76.96% │
│ runs timed out : 0 (0.00%) │ count coverage : 4.76 bits/tuple │
├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤
│ now trying : havoc │ favored items : 113 (10.38%) │
│ stage execs : 102/300 (34.00%) │ new edges on : 31 (2.85%) │
│ total execs : 14.1M │ total crashes : 0 (0 saved) │
│ exec speed : 236.0/sec │ total tmouts : 3449 (0 saved) │
├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
│ bit flips : 2/10.5k, 2/10.5k, 0/10.5k │ levels : 38 │
│ byte flips : 0/1315, 0/1314, 0/1312 │ pending : 0 │
│ arithmetics : 4/92.0k, 0/183k, 0/183k │ pend fav : 0 │
│ known ints : 0/11.8k, 0/49.9k, 0/73.5k │ own finds : 1052 │
│ dictionary : 0/0, 0/0, 0/0, 0/0 │ imported : 0 │
│havoc/splice : 237/12.4M, 0/0 │ stability : 100.00% │
│py/custom/rq : unused, unused, unused, unused ├───────────────────────┤
│ trim/eff : 4.22%/397k, 99.92% │ [cpu: 23%]│
└─ strategy: exploit ────────── state: in progress ──┘
Please see the API Documentation for more details.
The preferred way to use SBJson is by using CocoaPods. In your Podfile use:
pod 'SBJson', '~> 5.0.4'
SBJson is compatible with Carthage. Follow the Getting Started Guide for iOS.
github "SBJson/SBJson" == 5.0.4
An alternative that I no longer recommend is to copy all the source files (the
contents of the Classes folder) into your own Xcode project.
NSURLSessionDataDelegate to do chunked delivery.SBJson if you
have questions about how to use the library.SBJson practice Semantic Versioning, which means we do not break the API in major releases. If something requires a backwards-incompatible change, we release a new major version. (Hence why a library of less than 1k lines has more major versions than Emacs.)
I also try support a gradual migration from one major version to the
other by allowing the last three major versions to co-exist in the
same app without conflicts. The way to do this is putting the major
version number in all the library's symbols and file names. So if v6
ever comes out, the SBJson5Parser class would become
SBJson6Parser, etc.
BSD. See LICENSE for details.
141 followers · starred May 2013
91 followers · starred Jun 2010
989 followers · starred Jun 2010
431 followers · starred Jun 2010
This framework implements a strict JSON parser and generator in Objective-C.
Objective-C
3,713
1,199 commits
updated May 18, 2026
Chunk-based JSON parsing and generation in Objective-C.
SBJson's number one feature is stream/chunk-based operation. Feed the parser one or more chunks of UTF8-encoded data and it will call a block you provide with each root-level document or array. Or, optionally, for each top-level entry in each root-level array.
With this 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 full document has downloaded. You can also parse massive documents bit by bit so you don't have to keep them all in memory.
SBJson maps JSON types to Objective-C types in the following way:
| JSON Type | Objective-C Type |
|---|---|
| null | NSNull |
| string | NSString |
| array | NSMutableArray |
| object | NSMutableDictionary |
| true | -[NSNumber numberWithBool: YES] |
| false | -[NSNumber numberWithBool: NO] |
| number | NSNumber |
long long or unsigned long long if they fit,
to avoid rounding errors. For all other numbers we use the double
type, with all the potential rounding errors that entails.First define a simple block & an error handler. (These are just minimal examples. You should strive to do something better that makes sense in your application!)
SBJson5ValueBlock block = ^(id v, BOOL *stop) {
BOOL isArray = [v isKindOfClass:[NSArray class]];
NSLog(@"Found: %@", isArray ? @"Array" : @"Object");
};
SBJson5ErrorBlock eh = ^(NSError* err) {
NSLog(@"OOPS: %@", err);
exit(1);
};
Then create a parser and add data to it:
id parser = [SBJson5Parser parserWithBlock:block
errorHandler:eh];
id data = [@"[true," dataWithEncoding:NSUTF8StringEncoding];
[parser parse:data]; // returns SBJson5ParserWaitingForData
// block is not called yet...
// ok, now we add another value and close the array
data = [@"false]" dataWithEncoding:NSUTF8StringEncoding];
[parser parse:data]; // returns SBJson5ParserComplete
// the above -parse: method calls your block before returning.
Alright! Now let's look at something slightly more interesting.
This is useful for something like Twitter's feed, which gives you one JSON document per line. Here is an example of parsing many consequtive JSON documents, where your block will be called once for each document:
id parser = [SBJson5Parser 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 use a multiRootParser. But, all is not lost: if you are parsing a long array you can get the same effect by using an unwrapRootArrayParser:
id parser = [SBJson5Parser unwrapRootArrayParserWithBlock:block
errorHandler:eh];
// Note that this input contains A SINGLE top-level document
id data = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding];
[parser parse:data];
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.
I've run AFL++ on the sbjson binary for over 24 hours, with no crashes found. (I cannot reproduce the hangs reported when attempting to parse them manually.)
To reproduce, make sure you have Nix installed, then:
git checkout fuzz
nix-shell --command ./fuzz.sh
The shell.nix builds AFL++ 4.34c from source, configured to use
POSIX shared memory (shm_open + mmap) so it runs on macOS without
the System V IPC issues the packaged version has. The fuzz.sh script
builds an instrumented sbjson binary and starts afl-fuzz seeded with
the jsonchecker test data.
Here's the output after about 24 hours of fuzzing:
american fuzzy lop ++4.34c {default} (/tmp/sbjson-afl/sbjson) [explore]
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│ run time : 1 days, 0 hrs, 26 min, 51 sec │ cycles done : 57 │
│ last new find : 0 days, 0 hrs, 30 min, 9 sec │ corpus count : 1089 │
│last saved crash : none seen yet │saved crashes : 0 │
│ last saved hang : none seen yet │ saved hangs : 0 │
├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
│ now processing : 1055.224 (96.9%) │ map density : 8.85% / 76.96% │
│ runs timed out : 0 (0.00%) │ count coverage : 4.76 bits/tuple │
├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤
│ now trying : havoc │ favored items : 113 (10.38%) │
│ stage execs : 102/300 (34.00%) │ new edges on : 31 (2.85%) │
│ total execs : 14.1M │ total crashes : 0 (0 saved) │
│ exec speed : 236.0/sec │ total tmouts : 3449 (0 saved) │
├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
│ bit flips : 2/10.5k, 2/10.5k, 0/10.5k │ levels : 38 │
│ byte flips : 0/1315, 0/1314, 0/1312 │ pending : 0 │
│ arithmetics : 4/92.0k, 0/183k, 0/183k │ pend fav : 0 │
│ known ints : 0/11.8k, 0/49.9k, 0/73.5k │ own finds : 1052 │
│ dictionary : 0/0, 0/0, 0/0, 0/0 │ imported : 0 │
│havoc/splice : 237/12.4M, 0/0 │ stability : 100.00% │
│py/custom/rq : unused, unused, unused, unused ├───────────────────────┤
│ trim/eff : 4.22%/397k, 99.92% │ [cpu: 23%]│
└─ strategy: exploit ────────── state: in progress ──┘
Please see the API Documentation for more details.
The preferred way to use SBJson is by using CocoaPods. In your Podfile use:
pod 'SBJson', '~> 5.0.4'
SBJson is compatible with Carthage. Follow the Getting Started Guide for iOS.
github "SBJson/SBJson" == 5.0.4
An alternative that I no longer recommend is to copy all the source files (the
contents of the Classes folder) into your own Xcode project.
NSURLSessionDataDelegate to do chunked delivery.SBJson if you
have questions about how to use the library.SBJson practice Semantic Versioning, which means we do not break the API in major releases. If something requires a backwards-incompatible change, we release a new major version. (Hence why a library of less than 1k lines has more major versions than Emacs.)
I also try support a gradual migration from one major version to the
other by allowing the last three major versions to co-exist in the
same app without conflicts. The way to do this is putting the major
version number in all the library's symbols and file names. So if v6
ever comes out, the SBJson5Parser class would become
SBJson6Parser, etc.
BSD. See LICENSE for details.
141 followers · starred May 2013
91 followers · starred Jun 2010
989 followers · starred Jun 2010
431 followers · starred Jun 2010