- Overview
- Public interface
- JSONPath support (>= 1.9.9.7)
- Anonymous types support (>= 1.9.9.8)
- Integral types support (>= 2.0.0.0)
- Miscellaneous types support
- Known limitations / caveats
- Roadmap
- Background
- CFAQ
- Performance
- Test target POCOs
This is a minimalistic and fast JSON parser / deserializer, for full .NET.
The complete source code of the parser is pretty short (in a single source file less than 1,700 SLOC-long, not counting the comments) and comes with some speed tests and their sample data, all in the "JsonTest" and "TestData" folders.
The console test program includes a few unit tests (for both nominal cases vs. error cases), and it will attempt to execute them before the speed tests - see ParserTests.cs.
Do not hesitate to add more of the former (i.e., unit tests) and/or to raise here whatever issues you may find.
There's a link to this repository at json.org (in the C# implementations section).
This aims at parsing textual JSON data, and to deserialize it into our (strongly typed) POCOs, as fast as possible.
For now, the main tiers of interest for this parser are the desktop / server tiers. There are other JSON librairies with good performance, and already well tested / documented, which also support mobile / handheld devices that run more limited flavors of .NET (e.g., Json.NET and ServiceStack come to mind).
However, Sami was able to do a quick performance test on his Android device (as the code could luckily compile for it as-is), and came up with a few benchmark results (a dozen or so) which do seem encouraging - i.e., see this thread of Xamarin's Android forum :
http://forums.xamarin.com/discussion/comment/39011/#Comment_39011
Thus, starting with version 2.0.0.6, Android and WP8 are also supported (see NuGet section below).
Thank you Sami :)
For convenience :
Desktop version
- https://www.nuget.org/packages/System.Text.Json
- PM> Install-Package System.Text.Json
Mobile versions for Android and WP8
- https://www.nuget.org/packages/System.Text.Json.Mobile
- PM> Install-Package System.Text.Json.Mobile
Although it is promisingly fast, lightweight, and easy to use, please note this parser / deserializer is still mostly experimental.
For one simple thing to begin with, it is in need of more comprehensive JSON conformance tests.
That said, just feel free to fork / bugfix / augment / improve it at your own will.
Of course, I welcome your informed input and feedback.
Please read and accept the terms of the LICENSE, or else, do not use this library as-is.
Consists mainly in these six (three generic ones, and three non-generic ones) instance methods :
T Parse<T>(string input)
and
T Parse<T>(System.IO.TextReader input)
and
T Parse<T>(System.IO.Stream input)
and
object Parse(string input)
and
object Parse(System.IO.TextReader input)
and
object Parse(System.IO.Stream input)
The capability to parse JSON text coming thru a stream (reader) is clearly a must-have, past a certain size of payload - "have mercy on your CLR large object heap".
Note that if you don't care (i.e., don't need / don't want to bother) deserializing whatever input JSON into POCOs, you can then just call these methods with
object
for the generic type argument, as in, e.g. :
parser.Parse<object>(@" [ { ""greetings"": ""hello"" } ] ")
or, equivalently, just :
parser.Parse(@" [ { ""greetings"": ""hello"" } ] ")
It will then deserialize the input into a tree made of
Dictionary<string, object>
instances, for JSON objects which are unordered sets of name / value pairs, and of
List<object>
instances, for JSON arrays which are ordered collections of values.
The leaves will be from any of these types :
null type
bool
string
In this case of, say, "loosely typed" deserialization, you may ask : "But what about the JSON number literals in the input - why deserializing them as strings?"
I would then ask - "... in absence of more specific type information about the deserialization target, "who" is likely best placed to decide whether the number after the colon, in
"SomeNumber": 123.456
should be deserialized into a System.Single, a System.Double, or a System.Decimal (but obviously not into some integer) - is it this parser, or is it the application?"
In my opinion, in that case, it's the application.
(Also, one can read this very informative post of Eric Lippert about the so-called "null type".)
Starting with version 1.9.9.7, Stefan Gössner's JSONPath is also supported.
For a reference example, in four or five steps :
Step #1, have defined somewhere a target object model / type system to hydrate :
namespace Test
{
public class Data
{
public Store store { get; set; }
}
public class Store
{
public Book[] book { get; set; }
public Bicycle bicycle { get; set; }
}
public class Book
{
public string category { get; set; }
public string author { get; set; }
public string title { get; set; }
public decimal price { get; set; }
}
public class Bicycle
{
public string color { get; set; }
public decimal price { get; set; }
}
}
Step #2, in order to use this JSONPath facility don't forget to include, accordingly :
using System.Text.Json.JsonPath;
Step #3, have some input JSON to parse :
string input = @"
{ ""store"": {
""book"": [
{ ""category"": ""reference"",
""author"": ""Nigel Rees"",
""title"": ""Sayings of the Century"",
""price"": 8.95
},
{ ""category"": ""fiction"",
""author"": ""Evelyn Waugh"",
""title"": ""Sword of Honour"",
""price"": 12.99
},
{ ""category"": ""fiction"",
""author"": ""Herman Melville"",
""title"": ""Moby Dick"",
""isbn"": ""0-553-21311-3"",
""price"": 8.99
},
{ ""category"": ""fiction"",
""author"": ""J. R. R. Tolkien"",
""title"": ""The Lord of the Rings"",
""isbn"": ""0-395-19395-8"",
""price"": 22.99
}
],
""bicycle"": {
""color"": ""red"",
""price"": 19.95
}
}
}
";
(Optional) step #4, have an evaluator delegate ready to compile JSONPath member selectors or filter predicates that the JsonPathSelection's SelectNodes(...) method may come across :
JsonPathScriptEvaluator evaluator =
(script, value, context) =>
(value is Type) ?
// This holds: (value as Type) == typeof(Func<string, T, IJsonPathContext, object>),
// with T inferred by JsonPathSelection::SelectNodes(...)
ExpressionParser.Parse
(
(Type)value, script, true, typeof(Data).Namespace
).
Compile()
:
null;
where the delegate type JsonPathScriptEvaluator has been redefined here, as :
delegate object JsonPathScriptEvaluator(string script, object value, IJsonPathContext context)
Note there is a basic ( * ) lambda expression parser & compiler - ExpressionParser (adapted from Zhucai's "lambda-parser", at http://code.google.com/p/lambda-parser) defined in the namespace "System.Text.Json.JsonPath.LambdaCompilation" - used here as a helper to implement the above "evaluator".
(* N.B. : not all of the C# 3.0+ syntax is supported by ExpressionParser (e.g., the Linq Query Comprehension Syntax isn't) - only the most common expression forms, including unary / binary / ternary operators, array & dictionary indexers "[ ]", instance and static method calls, "is", "as", "typeof" type system operators, ... etc.)
Step #4 or #5, (a) parse and deserialize the input JSON into the target object model, (b) wrap a JsonPathSelection instance around the latter, and (c) invoke the JsonPathSelection's SelectNodes(...) method with the JSONPath expression of interest to query the object model :
// Step #5 (a)... parse and deserialize the input JSON into the target object model :
var typed = new JsonParser().Parse<Data>(input);
// Step #5 (b)... cache the JsonPathSelection and its lambdas compiled (on-demand)
// by the evaluator :
var scope = new JsonPathSelection(typed, evaluator);
// Step #5 (c)... invoke the SelectNodes method with the JSONPath expression
// to query the object model :
var nodes = scope.SelectNodes("$.store.book[?(@.title == \"The Lord of the Rings\")].price");
System.Diagnostics.Debug.Assert
(
nodes != null &&
nodes.Length == 1 &&
nodes[0].Value is decimal &&
nodes[0].As<decimal>() == 22.99m
);
Thus, the purpose of this "evaluator", passed here as an optional argument to the JsonPathSelection constructor, is for the SelectNodes(...) method to be able to compile on-demand whatever lambda expression delegates are required to implement JSONPath expressions for member selectors or filter predicates, such as
?(@.title == \"The Lord of the Rings\")
above.
In this same example, the lambda expression delegate compiled by the evaluator (and then cached into the "scope" JsonPathSelection instance) is of type
Func<string, Book, IJsonPathContext, object>
corresponding to the actual lambda expression script prepared behind the scene :
(string script, Book value, IJsonPathContext context) => (object)(value.title == "The Lord of the Rings")
There is therefore type inference - performed at run-time by the JsonPathSelection's SelectNodes(...) method - regarding the second argument (and only that one, named "value") of the evaluator-produced, cached delegates.
Finally, notice how those delegates' static return type is in fact System.Object (and not System.Boolean), for uniformity with the more general member selector expression, as used as an alternative to explicit names or indices.
More JSONPath usage examples (after JSON deserialization by System.Text.Json.JsonParser) can be found here.
E.g., the following JSONPath expressions work as expected, in version 1.9.9.7 and up :
$.store // The store
$['store'] // (Idem) The store
// (Involves an object member selector lambda)
$.[((@ is Data) ? \"store\" : (string)null)] // (Idem) The store
$.store.book[3].title // Title of the fourth book
// (Involves an object filter predicate lambda)
$.store.book[?(@.author == \"Herman Melville\")].price // Price of Herman Melville's book
$.store.book[*].author // Authors of all books in the store
$.store..price // Price of everything in the store
$..book[2] // Third book
// (Involves an array member (index) selector lambda)
$..book[(@.Length - 1)] // Last book in order
$..book[-1:] // (Idem) Last book in order
$..book[0,1] // First two books
$..book[:2] // (Idem) First two books
// (Involves an object filter predicate lambda)
$..book[?(@.isbn)] // All books with an ISBN
// (Idem)
$..book[?(@.price < 10m)] // All books cheaper than 10
References
- JSONPath - XPath for JSON (by Stefan Gössner)
- JSONPath expressions
- JSONPath examples
Online utilities
- JSONPath Expression Tester (by Curious Concept)
- JSONPath Online Evaluator (by Kazuki Hamasaki)
Starting with version 1.9.9.8, the deserialization into anonymous types instances is also supported. Here is an example to get started :
// Anonymous type instance prototype of the target object model,
// used for static type inference by the C# compiler (see below)
var OBJECT_MODEL = new
{
country = new // (Anonymous) country
{
name = default(string),
people = new[] // (Array of...)
{
new // (Anonymous) person
{
initials = default(string),
DOB = default(DateTime),
citizen = default(bool),
status = default(Status) // (Marital "Status" enumeration type)
}
}
}
};
var anonymous = new JsonParser().Parse
(
// Anonymous type instance prototype, passed in
// solely for type inference by the C# compiler
OBJECT_MODEL,
// Input
@"{
""country"": {
""name"": ""USA"",
""people"": [
{
""initials"": ""VV"",
""citizen"": true,
""DOB"": ""1970-03-28"",
""status"": ""Married""
},
{
""DOB"": ""1970-05-10"",
""initials"": ""CJ""
},
{
""initials"": ""RP"",
""DOB"": ""1935-08-20"",
""status"": ""Married"",
""citizen"": true
}
]
}
}"
);
System.Diagnostics.Debug.Assert(anonymous.country.people.Length == 3);
foreach (var person in anonymous.country.people)
System.Diagnostics.Debug.Assert
(
person.initials.Length == 2 &&
person.DOB > new DateTime(1901, 1, 1)
);
scope = new JsonPathSelection(anonymous, evaluator);
System.Diagnostics.Debug.Assert
(
(nodes = scope.SelectNodes(@"$..people[?(!@.citizen)]")).Length == 1 &&
nodes.As(OBJECT_MODEL.country.people[0])[0].initials == "CJ" &&
nodes.As(OBJECT_MODEL.country.people[0])[0].DOB == new DateTime(1970, 5, 10) &&
nodes.As(OBJECT_MODEL.country.people[0])[0].status == Status.Single
);
where the "evaluator" is the same as the one defined in the JSONPath section of this document.
Starting with version 2.0.0.0, the following integral types are supported (including as possible underlying types of programmer-defined enumeration types) :
(example)
- System.SByte (aka "sbyte" in C#) (>= 2.0.0.0)
- System.Byte (aka "byte")
- System.Int16 (aka "short")
- System.UInt16 (aka "ushort") (>= 2.0.0.0)
- System.Int32 (aka "int")
- System.UInt32 (aka "uint") (>= 2.0.0.0)
- System.Int64 (aka "long")
- System.UInt64 (aka "ulong") (>= 2.0.0.0)
- Starting with version 2.0.0.1, System.Guid is supported (example)
- Starting with version 2.0.0.2, some well-known nullable types are supported (example) - these are, in C# syntax :
- bool?
- char?
- sbyte?
- byte?
- short?
- ushort?
- int?
- uint?
- long?
- ulong?
- float?
- double?
- decimal?
- Guid?
- DateTime?
- DateTimeOffset?
- ... as well as any programmer-defined nullable enumeration type
- The current JsonParser's instance methods implementation is neither thread-safe or reentrant.
- (Work is underway to make the "Parse" methods of the public interface at least reentrant for any given JsonParser instance.)
Incoming "null"s are not yet recognized as a valid "value" (or rather, absence thereof) for nullable types members of the target POCO(s).(Support for such explicited "null"s will be added asap.)(fixed in version 2.0.0.5)
None really worth of the name for now (beyond what is mentioned in "known limitations" above).
However, one thing I would like to support as soon as I can, is the ability to deserialize into C#'s anonymous types. I've done it before, but I need to put more thinking into it (vs. my first, other attempt at it), in order to avoid the potential significant loss in performance I'm aware of.
Another, quite obvious, item on the wish list is to provide some support for custom deserialization. Design-wise, I do have a preference for a functional approach which would be based on (more or less) arbitrary "reviver" delegate types, for use by the parser's methods (for typical IoC / callback use cases).
Again, the main implementation challenge will be not drifting too much from the current parsing speed ballpark.
In any case, I don't plan to make this small JSON deserializer as general-purpose and extensible as Json.NET or ServiceStack's, I just want to keep it as simple, short, and fast as possible for my present and future needs (read on).
Pure parsing + deserialization speed isn't in fact my long term goal, or not for any arbitrary JSON input, anyway. For another, and broader project - still in design stage - that I have, I plan to use JSON as a "malleable" IR (intermediate representation) for code and meta data transformations that I'll have to make happen in-between a high level source language (e.g., C#, or F#,...) and the target CIL (or some other lower target).
Json.NET's deserialization performance is great, and so is ServiceStack's - really, they are, already - but I would like to have something of my own in my toolbox much smaller / more manageable (in terms of # of SLOC), and simpler to extend, for whatever unforeseen requirements of the deserialization process (from JSON text into CLR types and values) I may have to tackle.
This parser / deserializer is / was also a nice learning opportunity in regards to parsing JSON, and to verify by myself once again what I had read about and experienced many times before. That is: never try to merely guess about performance, but instead always do your best to measure and to find out where exactly the parsing and deserialization slowdowns (and memory consumption costs) actually come from.
(Could-be Frequently Asked Questions)
- Q : Isn't it a bit confusing, somehow, that the "Parse" methods of the public interface do actually more than just parse the input against the JSON syntax, but also perform the work that most other JSON implementations call "Deserialize"?
- A : Yes and no. It is indeed true that these "Parse" methods do more than just parse the input, but they have been named that way because this JsonParser is designed to remain only that : merely a JSON parser and deserializer, thus without any JSON serialization-related feature. By not naming them "Deserialize", this helps to avoid another otherwise possible confusion as to why there are no "Serialize" methods to be found anywhere, w.r.t. the dual operation (serialization vs. deserialization).
- Q : Do you foresee that you'll make any breaking changes to the public interface in the near-, mid-, or long-term?
- A : For most of it, no, I should not and I won't. The only JsonParser's instance methods that may be subject to change / to some refactoring (or disappear altogether) in the future, are those taking that last "IDictionary<Type, Func<...>> mappers" parameter (for now a rudimentary provision to support custom filtered deserialization use cases). So, all of the following are definitely going to stay around for as long as JsonParser is developed and maintained here :
JsonParser's (stable public interface)
object Parse(string input)
object Parse(TextReader input)
object Parse(Stream input)
object Parse(Stream input, Encoding encoding)
T Parse<T>(string input)
T Parse<T>(T prototype, string input)
T Parse<T>(TextReader input)
T Parse<T>(T prototype, TextReader input)
T Parse<T>(Stream input)
T Parse<T>(T prototype, Stream input)
T Parse<T>(Stream input, Encoding encoding)
T Parse<T>(T prototype, Stream input, Encoding encoding)
Feel free to send them to :
ysharp {dot} design {at} gmail {dot} com
Following in the table below: a few figures, the outcome average numbers (only) that I obtain from the tests provided here.
Consistently enough, I also obtain similar performance ratios for the same 4 parsers / deserializers when compared one-to-one, after I adapt (for this JsonParser doesn't provide object-to-JSON text serialization) and I run "the burning monk's" simple speed tester for JSON, which can be found at :
http://theburningmonk.com/2015/06/binary-and-json-benchmarks-updated-3
Speed Tests Results : Overview
This JsonParser versus... |
Microsoft's JavaScript Serializer |
James Newton-King's Json.NET |
Demis Bellot's ServiceStack |
This JsonParser |
Peter Ohler's "Oj" C extension to Ruby |
---|---|---|---|---|---|
Performance +/- % |
+ 425 % (faster) |
+ 127 % (faster) |
+ 33 % (faster) |
= | - 108 % (slower) |
Disclaimer
Note such figures (either the "burning monk's", or the following) can always - potentially - be much dependent on the test data at hand, and/or the way testing is performed.
Of course, YMMV, so it's always a good idea to make your own benchmarks, using your own test data, especially in the data "shape" you're interested in, and that you expect to encounter with a good probability in your application domain.
Other libraries, versions used ("the competition")
- Out-of-the-box .NET 4.0's JavaScriptSerializer
- Json.NET v5.0 r8, and
- ServiceStack v3.9.59
Before you try to run the speed tests against the test data provided, please note this repository does not include the binaries for the versions of Json.NET and ServiceStack mentioned (you can obtain those from their respective links above).
Executable target, and H/W used
.NET 4.0 target, on a humble Ideapad Intel Core i5 CPU @ 2.50GHz, 6 GB RAM, running Win7 64bit, with a ~ 98%..99% idle CPU (a nice enough personal laptop, but not exactly a beast of speed nowadays).
Just for comparison out of curiosity, on the third row of the table below I also give (this one measure only, for a glimpse) the throughput achieved, in the native code realm, by Peter Ohler's "Oj" ("Oj" - Optimized JSON : a C extension to Ruby) for 100,000 parses over his own JSON sample that I've reused to prepare this benchmark.
(Refer to _oj-highly-nested.json.txt, copied from Peter's: http://www.ohler.com/dev/oj_misc/performance_strict.html)
Speed Tests Results : Detailed
So, without further ado... (larger figure - # parses per second - means faster)
Test / JSON size / # Iterations / POCO or loosely-typed? |
Microsoft's JavaScript Serializer |
James Newton- King's Json.NET |
Demis Bellot's ServiceStack |
This JsonParser |
Peter Ohler's "Oj" C extension to Ruby |
---|---|---|---|---|---|
_oj-highly-nested.json 257 bytes 10,000 iter. Loosely-typed |
11.4 K parses/sec | 12.4 K parses/sec | N / A | 42.5 K parses/sec | N / A |
_oj-highly-nested.json 257 bytes 100,000 iter. Loosely-typed |
11.0 K parses/sec | 12.6 K parses/sec | N / A | 36.9 K parses/sec | 76.6 K parses/sec (informative; on his machine) |
boon-small.json 79 bytes 1,000,000 iter. POCO (see below) |
31.7 K parses/sec | 139.9 K parses/sec | 180.2 K parses/sec | 261.1 K parses/sec | N / A |
boon-small.json 79 bytes 10,000,000 iter. POCO (see below) |
33.1 K parses/sec | 143.3 K parses/sec | 182.5 K parses/sec | 271.0 K parses/sec | N / A |
tiny.json 127 bytes 10,000 iter. POCO (see below) |
18.2 K parses/sec | 40.0 K parses/sec | 80.0 K parses/sec | 178.6 K parses/sec | N / A |
tiny.json 127 bytes 100,000 iter. POCO (see below) |
18.5 K parses/sec | 90.9 K parses/sec | 133.3 K parses/sec | 173.9 K parses/sec | N / A |
tiny.json 127 bytes 1,000,000 iter. POCO (see below) |
18.4 K parses/sec | 101.0 K parses/sec | 147.0 K parses/sec | 169.5 K parses/sec | N / A |
dicos.json 922 bytes 10,000 iter. POCO (see below) |
N / A | 6.6 K parses/sec | 16.7 K parses/sec | 41.8 K parses/sec | N / A |
dicos.json 922 bytes 100,000 iter. POCO (see below) |
N / A | 7.0 K parses/sec | 19.1 K parses/sec | 39.7 K parses/sec | N / A |
dicos.json 922 bytes 1,000,000 iter. POCO (see below) |
N / A | 7.2 K parses/sec | 18.7 K parses/sec | 38.8 K parses/sec | N / A |
small.json 3.5 KB 10,000 iter. Loosely-typed |
1.5 K parses/sec | 4.5 K parses/sec | N / A | 8.8 K parses/sec | N / A |
small.json 3.5 KB 100,000 iter. Loosely-typed |
1.5 K parses/sec | Exception | N / A | 8.6 K parses/sec | N / A |
fathers.json 12.4 MB (single parse) POCO (see below) |
5.0 MB/sec | 26.0 MB/sec | 22.6 MB/sec | 45.5 MB/sec | N / A |
huge.json 180 MB (single parse) Loosely-typed |
3.0 MB/sec | Exception | N / A | 22.7 MB/sec | N / A |
The same, with the test files and timings details :
(smaller time means faster)
Peter's "Oj Strict Mode Performance" test
- "Loop" Test over Peter's "Oj Strict Mode Performance" sample (deserializing x times the JSON contained in the _oj-highly-nested.json.txt file = 257 bytes) - "loosely-typed" deserialization :
- Performed 10,000 iterations: in ~ 235 milliseconds ( * )
- vs. JavaScriptSerializer in ~ 875 milliseconds (... 272 % slower)
- vs. Json.NET in ~ 805 milliseconds (... 242 % slower)
- vs. ServiceStack... N / A
- ( * Which yields System.Text.Json.JsonParser's throughput : 10,982,905 bytes / second)
- Performed 100,000 iterations: in ~ 2.71 seconds ( * )
- vs. JavaScriptSerializer in ~ 9.05 seconds (... 239 % slower)
- vs. Json.NET in ~ 7.95 seconds (... 193 % slower)
- vs. ServiceStack... N / A
- ( * Which yields System.Text.Json.JsonParser's throughput : 9,486,895 bytes / second)
- _oj-highly-nested.json.txt comes from Peter's sample and tests, at:
- Performed 10,000 iterations: in ~ 235 milliseconds ( * )
I find the JSON data sample from Peter interesting for its non-trivial "shape", and the presence of these "highly nested" arrays (so to speak) at end of the payload :
{"a":"Alpha","b":true,"c":12345,"d":[true,[false,[-123456789,null],3.9676,
["Something else.",false],null]],"e":{"zero":null,"one":1,"two":2,"three":[3],"four":[0,1,2,3,4]},
"f":null,"h":{"a":{"b":{"c":{"d":{"e":{"f":{"g":null}}}}}}},
"i":[[[[[[[null]]]]]]]}
As for that "vs. ServiceStack in... N / A" :
unfortunately, quite unfamiliar with ServiceStack, I'm still trying to understand how, in absence of POCOs, to have it deserialize into merely trees of dictionaries + lists or arrays (just as we can do very easily using Json.NET, or Microsoft's JavaScriptSerializer, or this parser here).
Rick's "Boon" small test
- Rick's "Boon" small test, slightly modified (deserializing x times the JSON contained in the boon-small.json.txt file = 79 bytes) - with POCO target (1 class) :
- Performed 1,000,000 iterations: in ~ 3.83 seconds ( * )
- vs. JavaScriptSerializer in ~ 31.5 seconds (... 722 % slower)
- vs. Json.NET in ~ 7.15 seconds (... 86 % slower)
- vs. ServiceStack in ~ 5.55 seconds (... 45 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 20,632,018 bytes / second)
- Performed 10,000,000 iterations: in ~ 36.9 seconds ( * )
- vs. JavaScriptSerializer in ~ 302.3 seconds (... 719 % slower)
- vs. Json.NET in ~ 69.8 seconds (... 89 % slower)
- vs. ServiceStack in ~ 54.8 seconds (... 48 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 21,379,664 bytes / second)
- Performed 1,000,000 iterations: in ~ 3.83 seconds ( * )
Rick's original test can be found at :
http://rick-hightower.blogspot.com/2013/11/benchmark-for-json-parsing-boon-scores.html
Note Rick is one of our fellows from the Java realm - and from his own comparative figures that I eventually noticed, it does seem that Rick's "Boon" is also, indeed, pretty fast, among the number of Java toolboxes for JSON.
"Tiny JSON" test
- "Loop" Test over tiny JSON (deserializing x times the JSON contained in the tiny.json.txt file = 127 bytes) - with POCO target (1 class) :
- Performed 10,000 iterations: in ~ 56 milliseconds (pretty good) ( * )
- vs. JavaScriptSerializer in ~ 550 milliseconds (... 882 % slower)
- vs. Json.NET in ~ 250 milliseconds (... 346 % slower)
- vs. ServiceStack in ~ 125 milliseconds (... 123 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 22,321,428 bytes / second)
- Performed 100,000 iterations: in ~ 575 milliseconds (not bad) ( * )
- vs. JavaScriptSerializer in ~ 5.4 seconds (... 839 % slower)
- vs. Json.NET in ~ 1.1 seconds (... 91 % slower)
- vs. ServiceStack in ~ 750 milliseconds (... 30 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 21,865,184 bytes / second)
- Performed 1,000,000 iterations: in ~ 5.9 seconds (not bad either) ( * )
- vs. JavaScriptSerializer in 54.5 seconds (... 823 % slower)
- vs. Json.NET in ~ 9.9 seconds (... 67 % slower)
- vs. ServiceStack in ~ 6.8 seconds (... 15 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 21,594,966 bytes / second)
- tiny.json.txt
- Performed 10,000 iterations: in ~ 56 milliseconds (pretty good) ( * )
"Dicos JSON" test
- "Loop" Test over JSON "dictionaries" (deserializing x times the JSON contained in the dicos.json.txt file = 922 bytes) - with POCO target (1 class) :
- Performed 10,000 iterations: in ~ 239 milliseconds (pretty good) ( * )
- vs. JavaScriptSerializer... N / A
- vs. Json.NET in ~ 1,525 milliseconds (... 538 % slower)
- vs. ServiceStack in ~ 600 milliseconds (... 108 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 38,661,087 bytes / second)
- Performed 100,000 iterations: in ~ 2.52 seconds (not bad) ( * )
- vs. JavaScriptSerializer... N / A
- vs. Json.NET in ~ 14.2 seconds (... 463 % slower)
- vs. ServiceStack in ~ 5.25 seconds (... 108 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 36,623,067 bytes / second)
- Performed 1,000,000 iterations: in ~ 25.8 seconds (not bad either) ( * )
- vs. JavaScriptSerializer... N / A
- vs. Json.NET in ~ 2 minutes 19 seconds (... 439 % slower)
- vs. ServiceStack in ~ 53.4 seconds (... 107 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 35,788,984 bytes / second)
- dicos.json.txt
- Performed 10,000 iterations: in ~ 239 milliseconds (pretty good) ( * )
Note this reads "JavaScriptSerializer... N / A" for this test because I couldn't get Microsoft's JavaScriptSerializer to deserialize dicos.json.txt's data properly... and easily.
"Small JSON" test
- "Loop" Test over small JSON (deserializing x times the JSON contained in the small.json.txt file ~ 3.5 KB) - "loosely-typed" deserialization :
- Performed 10,000 iterations: in ~ 1.14 second (pretty good) ( * )
- vs. JavaScriptSerializer in ~ 6.7 seconds (... 488 % slower)
- vs. Json.NET in ~ 2.2 seconds (... 93 % slower)
- vs. ServiceStack... N / A
- ( * Which yields System.Text.Json.JsonParser's throughput : 31,174,408 bytes / second)
- Performed 100,000 iterations: in ~ 11.7 seconds (not bad) ( * )
- vs. JavaScriptSerializer in ~ 66.5 seconds (... 468 % slower)
- vs. Json.NET... OutOfMemoryException
- vs. ServiceStack... N / A
- ( * Which yields System.Text.Json.JsonParser's throughput : 30,318,786 bytes / second)
- small.json.txt being just a copy of the "{ "web-app": { "servlet": [ ... ] ... } }" sample, at:
- Performed 10,000 iterations: in ~ 1.14 second (pretty good) ( * )
"Fathers JSON" test
- "Fathers" Test (12 MB JSON file) - with POCO targets (4 distinct classes) :
- Parsed in ~ 285 milliseconds ( * )
- vs. JavaScriptSerializer in ~ 2.6 seconds (... 812 % slower)
- vs. Json.NET in ~ 500 milliseconds (... 75 % slower)
- vs. ServiceStack in ~ 575 milliseconds (... 101 % slower)
- ( * Which yields System.Text.Json.JsonParser's throughput : 45,494,340 bytes / second)
- Note: fathers.json.txt was generated using this nifty online helper :
- Parsed in ~ 285 milliseconds ( * )
The latter, "fathers" test, is the one with the results that intrigued me the most the very first few times I ran it - and it still does. However, I haven't taken the time yet to do more serious profiling to fully explain these timing differences that I didn't expect to be that significant.
They are also interesting to notice, if only when comparing Json.NET vs. ServiceStack.
"Huge JSON" test
- "Huge" Test (180 MB JSON file) - "loosely-typed" deserialization :
- Parsed in ~ 8.3 seconds ( * )
- vs. JavaScriptSerializer in ~ 62 seconds (... 646 % slower)
- vs. Json.NET... OutOfMemoryException
- vs. ServiceStack... N / A
- ( * Which yields System.Text.Json.JsonParser's throughput : 22,798,921 bytes / second)
- As for huge.json.txt, it is just a copy of this file:
- Parsed in ~ 8.3 seconds ( * )
These are used by some of the above tests :
// Used in the "boon-small.json" test
public class BoonSmall
{
public string debug { get; set; }
public IList<int> nums { get; set; }
}
// Used in the "tiny.json" test AND the unit tests
public enum Status { Single, Married, Divorced }
// Used in the "tiny.json" test AND the unit tests
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
// Both string and integral enum value representations can be parsed:
public Status Status { get; set; }
public string Address { get; set; }
// Just to be sure we support that one, too:
public IEnumerable<int> Scores { get; set; }
public object Data { get; set; }
// Generic dictionaries are also supported; e.g.:
// '{
// "Name": "F. Bastiat", ...
// "History": [
// { "key": "1801-06-30", "value": "Birth date" }, ...
// ]
// }'
public IDictionary<DateTime, string> History { get; set; }
// 1-char-long strings in the JSON can be deserialized into System.Char:
public char Abc { get; set; }
}
// Used in the "dicos.json" test
public enum SomeKey
{
Key0, Key1, Key2, Key3, Key4,
Key5, Key6, Key7, Key8, Key9
}
// Used in the "dicos.json" test
public class DictionaryData
{
public IList<IDictionary<SomeKey, string>> Dictionaries { get; set; }
}
// Used in the "dicos.json" test
// (adapted for Json.NET and ServiceStack to deserialize OK)
public class DictionaryDataAdaptJsonNetServiceStack
{
public IList<
IList<KeyValuePair<SomeKey, string>>
> Dictionaries { get; set; }
}
// Used in the "fathers.json" test
public class FathersData
{
public Father[] fathers { get; set; }
}
// Used in the "fathers.json" test
public class Someone
{
public string name { get; set; }
}
// Used in the "fathers.json" test
public class Father : Someone
{
public int id { get; set; }
public bool married { get; set; }
// Lists...
public List<Son> sons { get; set; }
// ... or arrays for collections, that's fine:
public Daughter[] daughters { get; set; }
}
// Used in the "fathers.json" test
public class Child : Someone
{
public int age { get; set; }
}
// Used in the "fathers.json" test
public class Son : Child
{
}
// Used in the "fathers.json" test
public class Daughter : Child
{
public string maidenName { get; set; }
}