A terrifyingly fast C# JSON framework with a focus CPU performance and minimizing allocations
- Reads and writes json data into a structured object graph with minimal allocations.
- High level API is one line of code to read or write JSON data.
- Low level API reads from and writes to text streams. Seamlessly integrates with cryptographic streams and can read and write data directly to and from sockets, files, etc.
- Maps complex objects to and from json values automatically without needing any annotations, markup, or codegen.
- Fully supports reading and writing multi-dimensional arrays, unlike other popular C# JSON libraries.
- Correctly serializes polymorphic type references.
- Optional value annotiation instructs Voorhees to ignore specific parameters and fields when serializing objects.
- Supports completely custom serializers/deserializers for arbitray types for full customizability of the JSON reading and writing process.
- Generates either pretty-printed JSON or condensed JSON.
You can read arbitrary json data into a JsonValue
structure using the JsonMapper.FromJson
method. This method operates on TextReader
streams, allowing for reading from strings, files, network messages, and more with a shared inteface.
using Voorhees;
// Load a json string into a JsonValue object graph.
JsonValue jsonValue = JsonMapper.FromJson<JsonValue>("{ \"someIntValue\": 3}");
Console.WriteLine(jsonValue.Type); // JsonValueType.Object
Console.WriteLine(jsonValue["someIntValue"].Type); // JsonValueType.Int
Console.WriteLine((int)jsonValue["someIntValue"]); // 3
You can also map json data to a matching C# structure using the generic version of JsonMapper.FromJson<T>
. This is most useful for reading in structured data into existing corresponding C# types.
using Voorhees;
struct ExampleStructure {
public int someIntValue;
}
TextReader json = new StringReader("{ \"someIntValue\": 3}");
ExampleStructure mappedValue = JsonMapper.FromJson<ExampleStructure>(json);
Console.WriteLine(mappedValue.someIntValue); // 3
JsonMapper
supports loading values into built-in collection types like Dictionary<T, U>
, List<T>
, arrays, and more:
string dictJson = "{\"one\": 1, \"two\": 2}";
Dictionary<string, int> numberNamesToInts = JsonMapper.FromJson<Dictionary<string, int>>(dictJson);
Console.WriteLine(numberNamesToInts.Count); // 2
Console.WriteLine(numberNamesToInts["one"]); // 1
Console.WriteLine(numberNamesToInts["two"]); // 2
List<int> mappedList = JsonMapper.FromJson<List<int>>("[1, 2, 3]");
Console.WriteLine(mappedList.Count); // 3
Console.WriteLine(mappedList[0]); // 1
Console.WriteLine(mappedList[1]); // 2
Console.WriteLine(mappedList[2]); // 3
int[] mappedArray = JsonMapper.FromJson<int[]>("[1, 2, 3]");
Console.WriteLine(mappedArray.Length); // 3
Console.WriteLine(mappedArray[0]); // 1
Console.WriteLine(mappedArray[1]); // 2
Console.WriteLine(mappedArray[2]); // 3
You can convert a JsonValue object into the matching JSON string using JsonMapper
:
using Voorhees;
JsonValue objectValue = new JsonValue {
{ "one", 1 },
{ "two", 2 },
{ "three", 3 }
};
string jsonObject = JsonMapper.ToJson(objectValue);
Console.WriteLine(jsonObject); // {"one":1,"two":2,"three":3}
JsonValue arrayValue = new JsonValue {1, 2, 3};
string jsonArray = JsonMapper.ToJson(arrayValue);
Console.WriteLine(jsonObject); // [1,2,3]
JsonMapper
also supports mapping arbitrary C# data types in addition to JsonValue
.
using Voorhees;
struct Player {
public string name;
public int gold;
public float ageYears;
}
Player Bilbo = new Player {
name = "Bilbo Baggins",
gold = 99,
ageYears = 111.32f
};
string bilboJson = JsonMapper.ToJson(Bilbo);
Console.WriteLine(bilboJson); // {"name":"Bilbo Baggins","gold":99,"ageYears":111.32}
int[] fibonacci = {1, 1, 2, 3, 5, 8};
string fibonacciJson = JsonMapper.ToJson(fibonacci);
Console.WriteLine(fibonacciJson); // [1,1,2,3,5,8]
By default the JSON generated by JsonMapper does not have any whitespace. This makes it as dense and small as possible, but not very convenient to read. To generate more readable JSON results, you can optionally enable pretty-printing.
using Voorhees;
JsonValue objectValue = new JsonValue {
{ "one", 1 },
{ "two", 2 },
{ "three", 3 }
};
string jsonObject = JsonMapper.ToJson(objectValue, prettyPrint: true);
Console.WriteLine(jsonObject);
/* Prints the following:
{
"one": 1,
"two": 2,
"three": 3
}
*/
JsonValue
is a discriminated union type that represents a JSON value. JsonValue
represents a value that is either a bool
, int
, double
, string
, List<JsonValue>
or Dictionary<string, JsonValue>
. The Type
parameter on a JsonValue
instance returns an enum that indicates what the contained type is.
Creating JsonValue instances
JsonValue
has a number of implicit conversion constructors, and is compatible with the C# syntactic sugar for declaring list and dictionary literals:
JsonValue boolValue = false;
JsonValue intValue = 3;
JsonValue doubletValue = 3.5;
JsonValue stringValue = "lorem ipsum";
JsonValue listValue = new JsonValue {1, 2, 3};
JsonValue objectValue = new JsonValue {{"one", 1}, {"two", 2}};
// These can be combined to create complex structure literals:
JsonValue complexValue = new JsonValue {
{"intValue", 42},
{"boolValue", true},
{"twoTranslations", new JsonValue {
{"es", "dos"},
{"cn", "si"},
{"jp", "ni"},
{"fr", "deux"}
}
},
{"somePrimes", new JsonValue {2, 3, 5, 7, 11}}
};
JsonValue
instances also define a number of implicit conversion operators to convert to an instance of the underlying basic type.
// assuming the above JsonValue definitions...
Console.WriteLine((bool)boolValue); // false
Console.WriteLine((int)intValue); // 3
Console.WriteLine((double)doubleValue); // 3.5
Console.WriteLine((string)stringValue); // lorem ipsum
JSON value reading and writing behavior can be completely customized by providing importer and exporter functions. These custom functions are registered to a JsonMapper
instance via the RegisterImporter
and RegisterExporter
methods. Reading and writing with a JsonMapper
instance (using any registered custom parsing functions) is done through the Read()
and Write()
methods, rather than the static JsonMapper.ToJson()
and JsonMapper.FromJson()
functions.
The Read
and Write
methods of JsonMapper
operate on TextReader
and TextWriter
streams. This integrates with CryptoStream
subclasses and allows for reading and writing to files, sockets, etc. TextReader
streams are wrapped in a JsonTokenReader
instance, while TextWriter
streams are wrapped in a JsonTokenWriter
instance. These wrappers provide json-specific reading and writing methods.
using Voorhees;
class MyNumber {
public int value;
}
// Creating a mapper instance allows us to customize mapping behavior.
var mapper = new JsonMapper();
// Parse an integer as a MyNumber instance.
mapper.RegisterImporter<MyNumber>(reader => {
return new MyNumber {
value = int.Parse(reader.ConsumeNumber())
};
});
// Write MyNumber values as integers, rather than objects
mapper.RegisterExporter((myNum, tokenWriter) => {
tokenWriter.Write(myNum.value);
});
var num = new MyNumber { value = 42 };
// Write an instance
// Writes an instance of MyNumber to the StringBuilder via the StringWriter and JsonTokenWriter.
var jsonBuilder = new StringBuilder();
using (var stringWriter = new StringWriter(jsonBuilder)) {
var tokenWriter = new JsonTokenWriter(stringWriter, prettyPrint:false);
mapper.Write(num, tokenWriter);
}
string json = jsonBuilder.ToString(); // "42"
// Read a MyNumber instance from the json string we just made.
using (var stringReader = new StringReader(json)) {
var tokenReader = new JsonTokenReader(stringReader);
num = mapper.Read<MyNumber>(tokenReader)
}