What is KGySoft.Json

First of all, what it is not: It’s not a serializer (though see the Examples section of the JsonValue type). It is rather a simple Domain Object Model (DOM) for JSON (or LINQ to JSON if you like), which makes possible to manipulate JSON content in memory in an object-oriented way (similarly to XDocument and its friends for XML).

What’s wrong with JSON.NET?

Nothing. It knows everything (and more). It was just too heavy-weight for my needs and I didn’t like that a lot of libraries reference different versions of it, causing version conflicts. Note though that it’s not the fault of JSON.NET; it’s just the consequence of popularity (the same could be true even for this library if it was so popular… but I don’t really think it could be a real issue anytime soon).

What’s wrong with System.Text.Json?

Well, it’s a bit different thing. As an in-memory JSON tool, it is read-only (JsonDocument/JsonElement) so you cannot build in-memory JSON content with it. It was introduced with .NET Core 3.0, so below that you have to use NuGet packages, which may lead to the same issue as above (but even with NuGet, it’s not supported below .NET Framework 4.6.1). Apart from those issues, it’s really fast, and mostly allocation free (well, as long as your source is already in UTF8 and you don’t access string elements).

Download

In Visual Studio

The preferred way is by the NuGet Package Manager in Visual Studio

  • You can use either the Package Manager Console:
    PM> Install-Package KGySoft.Json
  • Or the Package Manger GUI by the Manage NuGet Packages… context menu item of the project.

Direct Download

Alternatively, you can download the package directly from nuget.org.

If you prefer a .zip file containing the binaries see the releases on GitHub.

Source Code

The source is available on GitHub.

You can find both the source code and the binaries as .zip files among the releases.

Examples

Tip: See also the examples at the Remarks section of the JsonValue type.

Simple Syntax

The JsonValue type is the base building block of JSON content, which can be considered as a representation of a JavaScript variable (see also the JsonValueType enumeration). It also behaves somewhat similarly to JavaScript:

JsonValue value = default; // or: value = JsonValue.Undefined; value = new JsonValue();

Console.WriteLine(value); // undefined
Console.WriteLine(value.Type); // Undefined
Console.WriteLine(value.IsUndefined); // True
JsonValue value = JsonValue.Null; // or: value = (bool?)null; value = (string?)null; etc.

Console.WriteLine(value); // null
Console.WriteLine(value.Type); // Null
Console.WriteLine(value.IsNull); // True
JsonValue value = true; // or: value = JsonValue.True; value = new JsonValue(true);

Console.WriteLine(value); // true
Console.WriteLine(value.Type); // Boolean
Console.WriteLine(value.AsBoolean); // True
JsonValue value = 1.23; // or: value = new JsonValue(1.23);

Console.WriteLine(value); // 1.23
Console.WriteLine(value.Type); // Number
Console.WriteLine(value.AsNumber); // 1.23

Note: A JavaScript number is always a double-precision 64-bit binary format IEEE 754 value, which has the same precision as the .NET Double type. Though supported, it is not recommended to encode any numeric .NET type as a JSON Number (eg. Int64 or Decimal) because when such a JSON content is processed by JavaScript the precision might be silently lost.

// Using a long value beyond double precision
long longValue = (1L << 53) + 1;

JsonValue value = longValue; // this produces a compile-time warning about possible loss of precision
value = longValue.ToJson(asString: false); // this is how the compile-time warning can be avoided

Console.WriteLine(value); // 9007199254740993
Console.WriteLine($"{value.AsNumber:R}"); // 9007199254740992 - this is how JavaScript interprets it
Console.WriteLine(value.AsLiteral); // 9007199254740993 - this is the actual stored value
JsonValue value = "value"; // or: value = new JsonValue("value");

Console.WriteLine(value); // "value"
Console.WriteLine(value.Type); // String
Console.WriteLine(value.AsString); // value
JsonValue array = new JsonArray { true, 1, 2.35, JsonValue.Null, "value" };
// which is the shorthand of: new JsonValue(new JsonArray { JsonValue.True, new JsonValue(1), new JsonValue(2.35), JsonValue.Null, new JsonValue("value") });

Console.WriteLine(array); // [true,1,2.35,null,"value"]
Console.WriteLine(array.Type); // Array
Console.WriteLine(array[2]); // 2.35
Console.WriteLine(array[42]); // undefined
JsonValue obj = new JsonObject
{
    ("Bool", true), // which is the shorthand of new JsonProperty("Bool", new JsonValue(true))
    // { "Bool", true }, // alternative syntax on platforms where ValueTuple types are not available
    ("Number", 1.23),
    ("String", "value"),
    ("Object", new JsonObject
    {
       ("Null", JsonValue.Null),
       ("Array", new JsonArray { 42 }),
    })
};

Console.WriteLine(obj); // {"Bool":true,"Number":1.23,"String":"value","Object":{"Null":null,"Array":[42]}}
Console.WriteLine(obj.Type); // Object
Console.WriteLine(obj["Object"]["Array"][0]); // 42
Console.WriteLine(obj["UnknownProperty"]); // undefined

Though the JsonValue type has a JavaScript-like approach (eg. it has a single AsNumber property with nullable double return type for any numbers) the JsonValueExtensions class provides .NET type specific conversions for most .NET numeric types, including Int64, Decimal and more. Additionally, it offers conversions for Enum, DateTime, DateTimeOffset, TimeSpan and Guid types as well.

// Use the ToJson extension methods to convert common .NET types to JsonValue
var obj = new JsonObject
{
    ("Id", Guid.NewGuid.ToJson()),
    ("Timestamp", DateTime.UtcNow.ToJson(JsonDateTimeFormat.UnixMilliseconds)),
    ("Status", someEnumValue.ToJson(JsonEnumFormat.LowerCaseWithHyphens)),
    ("Balance", someDecimalValue.ToJson(asString: true))
};

To obtain the values you can use 3 different approaches:

// 1.) TryGet... methods: bool return value and an out parameter
if (obj["Balance"].TryGetDecimal(out decimal value)) {}

// 2.) As... methods: nullable return type
decimal? valueOrNull = obj["Balance"].AsDecimal(); // or: AsDecimal(JsonValueType.String) to accept strings only

// 3.) Get...OrDefault: non-nullable return type;
decimal balance = obj["Balance"].GetDecimalOrDefault(); // or: GetDecimalOrDefault(-1m) to specify a default value

Tip: There are several predefined formats for enums (see JsonEnumFormat), DateTime and DateTimeOffset types (see JsonDateTimeFormat) and TimeSpan values (see JsonTimeSpanFormat).


Writing JSON

Converting a JsonValue (or JsonArray/JsonObject) to a JSON string is as easy as calling the ToString method. To produce an indented result you can pass a string to the ToString method. Alternatively, you can use the WriteTo method to write the JSON content into a Stream (you can also specify an Encoding), TextWriter or StringBuilder.

JsonValue obj = new JsonObject
{
    ("Bool", true), // which is the shorthand of new JsonProperty("Bool", new JsonValue(true))
    // { "Bool", true }, // alternative syntax on platforms where ValueTuple types are not available
    ("Number", 1.23),
    ("String", "value"),
    ("Object", new JsonObject
    {
       ("Null", JsonValue.Null),
       ("Array", new JsonArray { 42 }),
    })
};

obj.WriteTo(someStream, Encoding.UTF8); // to write it into a stream (UTF8 is actually the default encoding)
Console.WriteLine(obj.ToString("  ")); // to print a formatted JSON using two spaces as indentation

The example above prints the following in the Console:

{
  "Bool": true,
  "Number": 1.23,
  "String": "value",
  "Object": {
    "Null": null,
    "Array": [
      42
    ]
  }
}

Parsing JSON

Use the JsonValue.Parse/TryParse methods to parse a JSON document from string, TextReader or Stream. You can also specify an Encoding for the Stream overload. If you expect the result to be an array or an object, then you can find these methods on the JsonArray and JsonObject types as well.

As you could see above navigation in a parsed object graph is pretty straightforward. You can use the int indexer for arrays and the string indexer for objects. Using an invalid array index or property name returns an undefined value:

var json = JsonValue.Parse(someStream);

// a possible way of validation
var value = json["data"][0]["id"];
if (value.IsUndefined)
    throw new ArgumentException("Unexpected content");
// ... do something with value

// alternative way
long id = json["data"][0]["id"].AsInt64() ?? throw new ArgumentException("Unexpected content");

Manipulating JSON

JsonValue is a read-only struct but JsonArray and JsonObject types are mutable classes. They implement some generic collection interfaces so they support LINQ extension methods.

JsonValue value = JsonValue.Parse(someStream);

JsonObject? toBeChanged = value["data"][0].AsObject;
// ... do null check if needed
toBeChanged["newProp"] = 123; // or: toBeChanged.Add("newProp", 123);

// Note that though JsonValue is a readonly struct, the changes are visible even from the original value.
// It's because we didn't replace any existing objects just appended one of them.
Console.WriteLine(value["data"][0]["newProp"].Type) // Number

Tip: JsonObject implements both IList<JsonProperty> and IDictionary<string, JsonValue> so without casting or specifying some type arguments many LINQ methods might be ambiguous on a JsonObject instance. To avoid ambiguity and to keep also the syntax simple you can perform the LINQ operations on its Entries property.

Help

Browse the online documentation for examples and detailed descriptions.

License

This repository is under the KGy SOFT License 1.0, which is a permissive GPL-like license. It allows you to copy and redistribute the material in any medium or format for any purpose, even commercially. The only thing is not allowed is to distribute a modified material as yours: though you are free to change and re-use anything, do that by giving appropriate credit. See the LICENSE file for details.