Repository navigation
Recipes
Working patterns, each with the output it gives. The outputs shown were produced with 3.0.2.
Read the input as text and stream the output, so a large document never exists twice in memory:
using System.IO;
using JsonPrettyPrinterPlus;
var printer = new JsonPrettyPrinter();
var json = File.ReadAllText("data.min.json");
using (var output = File.CreateText("data.json"))
{
printer.PrettyPrint(json, output);
output.WriteLine(); // a final line break, which the printer does not write
}File.ReadAllText removes a byte order mark and File.CreateText writes UTF-8 without one. To format in place, write to a temporary file and move it over the original.
A .NET 10 file-based app that reads standard input and writes standard output:
#:package JsonPrettyPrinter@3.0.2
using System;
using JsonPrettyPrinterPlus;
var printer = new JsonPrettyPrinter();
printer.PrettyPrint(Console.In.ReadToEnd(), Console.Out);
Console.Out.WriteLine();type data.min.json | dotnet run pretty.cs(cat on Linux and macOS.) Run dotnet build pretty.cs once and then dotnet run pretty.cs --no-build to skip the build check on each call.
var body = await response.Content.ReadAsStringAsync();
if (logger.IsEnabled(LogLevel.Debug))
logger.LogDebug("Response from {Url}:{NewLine}{Body}", url, Environment.NewLine, body.PrettyPrintJson());The IsEnabled guard keeps the formatting off the hot path when debug logging is off. A body that is not JSON (an HTML error page, say) still comes back as text, indented at every bracket it happens to contain. Only a closing bracket with no opener throws, so catch FormatException if the body could be anything.
using System.Text.Json;
static string PrettyOrError(string text)
{
try
{
using var _ = JsonDocument.Parse(text);
return text.PrettyPrintJson();
}
catch (JsonException e)
{
return "Not JSON: " + e.Message;
}
}JsonDocument.Parse is strict: {"a":1,} fails with The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. LineNumber: 0 | BytePositionInLine: 7. To accept trailing commas and comments, pass a JsonDocumentOptions with AllowTrailingCommas and CommentHandling = JsonCommentHandling.Skip, and then pretty print the parsed node's ToJsonString() rather than the original text, or the comments come through (Not a validator).
Two documents that differ only in whitespace pretty print to the same text, so an assertion can compare the formatted forms and show a readable diff when it fails:
Assert.That(actual.PrettyPrintJson(), Is.EqualTo(expected.PrettyPrintJson()));This is a text comparison: key order, number formatting (1.0 against 1) and escapes (\u00e9 against é) all count as differences. For a semantic comparison use JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected)), which ignores whitespace and key order, and pretty print only for the failure message.
Snapshot files are easier since 3.0 because the line ending is a line feed on every OS. If a snapshot goes through Git with autocrlf on, store it with eol=lf in .gitattributes or normalise line endings before comparing.
For a log line or a test name, put everything on one line with spaces instead of line breaks:
var oneLine = new JsonPrettyPrintOptions { IndentSize = 0, NewLine = " " };
"""{"a":1,"b":[1,2],"c":{}}""".PrettyPrintJson(oneLine);
// { "a": 1, "b": [ 1, 2 ], "c": {} }The printer only adds whitespace. To remove it, parse and write back:
using System.Text.Json.Nodes;
var compact = JsonNode.Parse("""{ "a" : [ 1 , 2 ] }""")!.ToJsonString(); // {"a":[1,2]}A minimal API that returns whatever it is sent, formatted:
app.MapPost("/pretty", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var json = await reader.ReadToEndAsync();
try
{
return Results.Text(json.PrettyPrintJson(), "application/json");
}
catch (FormatException e)
{
return Results.BadRequest(e.Message);
}
});Limit the request body size ([RequestSizeLimit] or the Kestrel option) before exposing this to the internet, because the input has to fit in memory. PrettyPrintJson() is safe to call from concurrent requests.
PrettyPrintJson(options) creates a printer per call. In a hot path with fixed options, keep one per thread:
using System.Threading;
private static readonly ThreadLocal<JsonPrettyPrinter> Printer =
new(() => new JsonPrettyPrinter(new JsonPrettyPrintOptions { IndentSize = 2 }));
public static string Pretty(string json) => Printer.Value!.PrettyPrint(json);Any serialiser's compact output can go through the printer.
Newtonsoft.Json:
var pretty = JsonConvert.SerializeObject(order).PrettyPrintJson();(Formatting.Indented gives Newtonsoft's own layout instead: two spaces and Environment.NewLine.)
PowerShell, after loading the assembly as Getting started shows:
$compact = [ordered]@{ name = 'Ada'; tags = @('math') } | ConvertTo-Json -Compress
[JsonPrettyPrinterPlus.PrettyPrinterExtensions]::PrettyPrintJson($compact){
"name": "Ada",
"tags": [
"math"
]
}
System.Text.Json's own objects have compact text forms too:
var fromNode = node.ToJsonString().PrettyPrintJson();
var fromElement = element.GetRawText().PrettyPrintJson();Set Console.OutputEncoding = System.Text.Encoding.UTF8 before writing text with characters outside ASCII. The printer keeps them as they are, and the default console code page may not show them.
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