Repository navigation
Serialisation Helpers
ToJson() and DeserializeFromJson<T>() in the namespace JsonPrettyPrinterPlus.JsonSerialization are thin wrappers over System.Text.Json.JsonSerializer. They exist so that "object to readable JSON" is one call. They have run on System.Text.Json since 2.0.0; before that they used JavaScriptSerializer, and Versions and upgrading lists what that changed.
using JsonPrettyPrinterPlus.JsonSerialization;
var thing = new { Name = "Mark", Tags = new[] { "a", "b" }, When = new DateTime(2010, 1, 1), Nothing = (string?)null, Price = 9.5m };
thing.ToJson();
// {"Name":"Mark","Tags":["a","b"],"When":"2010-01-01T00:00:00","Nothing":null,"Price":9.5}
thing.ToJson(prettyPrint: true);{
"Name": "Mark",
"Tags": [
"a",
"b"
],
"When": "2010-01-01T00:00:00",
"Nothing": null,
"Price": 9.5
}
Property names keep their C# casing, a DateTime is ISO 8601 (with an offset for a DateTimeOffset: 2010-01-01T00:00:00-06:00), a Guid is its usual text, an enum is its number, and a null property is null. ((object?)null).ToJson() is the text null. The object is serialised as its runtime type, so a base-typed variable still writes every property of the actual object. Dictionaries and lists work as you would expect.
using System.Text.Json;
var camel = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
thing.ToJson(camel, prettyPrint: true);{
"name": "Mark",
"tags": [
"a",
"b"
],
"when": "2010-01-01T00:00:00",
"nothing": null,
"price": 9.5
}
Anything System.Text.Json accepts goes in the options: naming policies, converters, DefaultIgnoreCondition, enums as strings. Passing null means the library defaults. WriteIndented = true in your options is honoured, but it gives System.Text.Json's layout (two spaces, Environment.NewLine); prettyPrint: true gives this library's and wins if both are set.
By default System.Text.Json escapes ", ', <, >, & and every character outside ASCII as \uXXXX:
new { s = "<b>&'é\"</b>" }.ToJson();
// {"s":"\u003Cb\u003E\u0026\u0027\u00E9\u0022\u003C/b\u003E"}That is valid JSON and every parser reads it back, but it is not what you want to look at. Relax it:
using System.Text.Encodings.Web;
var readable = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
new { s = "<b>&'é\"</b>" }.ToJson(readable);
// {"s":"<b>&'é\"</b>"}The "unsafe" in the name means the output is not safe to paste into HTML or JavaScript without further encoding. For files, logs and APIs it is the encoder you want.
The overloads without a JsonTypeInfo<T> use reflection. On net10.0 they carry RequiresUnreferencedCode and RequiresDynamicCode, so a project with PublishTrimmed or PublishAot gets warnings IL2026 and IL3050 at every call, and under native AOT the call fails at run time with InvalidOperationException: Reflection-based serialization has been disabled for this application. Use a JsonSerializerContext instead:
using System.Text.Json.Serialization;
public sealed class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
public DateTime Born { get; set; }
public string[] Tags { get; set; } = Array.Empty<string>();
}
[JsonSerializable(typeof(Person))]
internal partial class PersonContext : JsonSerializerContext
{
}
var person = new Person { Name = "Ada", Age = 36, Born = new DateTime(1815, 12, 10), Tags = new[] { "math" } };
var json = person.ToJson(PersonContext.Default.Person, prettyPrint: true);
var back = json.DeserializeFromJson(PersonContext.Default.Person);{
"Name": "Ada",
"Age": 36,
"Born": "1815-12-10T00:00:00",
"Tags": [
"math"
]
}
.NET 10 file-based apps (dotnet run app.cs) enable native AOT by default, so the reflection overloads throw there even under dotnet run. Either use a context as above, or put #:property PublishAot=false at the top of the file.
var person = """{"Name":"Ada","Age":36,"Born":"1815-12-10T00:00:00","Tags":["math"]}""".DeserializeFromJson<Person>();Property matching is case-sensitive by default. For camel-cased input, pass options:
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var person = """{"name":"Ada","age":36}""".DeserializeFromJson<Person>(options);DeserializeFromJson<object>() returns a JsonElement, and DeserializeFromJson<Dictionary<string, JsonElement>>() a dictionary of them. The text null deserialises to null for a reference type. Text that is not JSON throws JsonException with the position, for example 'n' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1. Null text throws ArgumentNullException.
On .NET Framework the package loads its netstandard2.0 build, and the System.Text.Json package there formats floating-point numbers differently from .NET 10. From the package's golden recordings of 3.0.1:
| Value | .NET Framework 4.8 | .NET 10 |
|---|---|---|
0.1.ToJson() |
0.10000000000000001 |
0.1 |
1e300.ToJson() |
1.0000000000000001E+300 |
1E+300 |
(-0.0).ToJson() |
0 |
-0 |
0.1f.ToJson() |
0.100000001 |
0.1 |
"1e400".DeserializeFromJson<double>() |
throws JsonException
|
Infinity |
Both forms read back as the same number. Compare parsed values, not text, when the same code runs on both.
On net10.0 the helpers use the System.Text.Json in the framework and the package has no dependencies. On netstandard2.0 the package references System.Text.Json 10.0.12, so .NET Framework and older .NET Core projects get it, and its own dependencies, from NuGet. That reference is the only reason the netstandard2.0 build has a dependency tree; the printer itself needs nothing beyond System.Memory. Keeping the helpers on netstandard2.0 was a deliberate choice for 3.0, because the projects most likely to want a one-liner are the older ones.
Nothing ties the printer to System.Text.Json. Any serialiser's compact output can go through PrettyPrintJson(): Recipes has Newtonsoft.Json and PowerShell examples.
This wiki describes JsonPrettyPrinter 3.0.2 and was last updated on 2026-09-30. The library is MIT licensed. Report problems in the issues.
Using it
- Getting started
- API reference
- Output format
- Not a validator
- Serialisation helpers
- Recipes
- Performance and threading
- FAQ
The releases
Contributing
Elsewhere