What the options change
The formatter reads the JSON with JsonDocument and writes it with Utf8JsonWriter, the writer JsonSerializer.Serialize uses. Each option on this page is a property you can set in .NET:
| Option | Default | What it does |
|---|---|---|
WriteIndented (Indented on JsonWriterOptions) | false | One value or property per line. With false the output is minified: no spaces or line breaks at all. |
IndentCharacter, IndentSize (.NET 9+) | space, 2 | The indentation: a space or a tab, repeated 0 to 127 times per level. |
NewLine (.NET 9+) | Environment.NewLine | The line break, \n or \r\n. The default depends on the OS: the same code writes \r\n on Windows and \n on Linux. Set it to get the same output everywhere. |
Encoder | JavaScriptEncoder.Default | Which characters are written as \uXXXX. See the next section. |
Indented output puts ": " between a name and its value and writes empty objects and arrays as {} and []. There is no line break at the end.
Escaping: the default encoder and UnsafeRelaxedJsonEscaping
The default encoder (used when Encoder is null) escapes everything outside printable ASCII, plus the characters that are unsafe in HTML. UnsafeRelaxedJsonEscaping leaves those as they are. That is fine for an API response, and unsafe if the JSON goes into an HTML page:
| Character | Default | UnsafeRelaxedJsonEscaping |
|---|---|---|
é, ü, ä¸ | é, ü, ä¸ | as they are |
< > & ' + ` | < > & ' + ` | as they are |
" inside a string | " | \" |
| emoji such as 😀 | 😀 | 😀 (still escaped) |
| no-break space, U+2028, U+FEFF |  , 
,  | still escaped |
| line feed, tab, backslash | \n, \t, \\ | \n, \t, \\ |
/ | as it is | as it is |
Hex digits are uppercase. Every string is unescaped when it is read and escaped again when it is written, so "A" comes out as "A" and "\/" as "/". The relaxed encoder also escapes control characters, private-use and unassigned code points, and everything above U+FFFF.
Numbers stay as written
JsonDocument and JsonNode keep the text of each number, so formatting never rounds: 1.50, 1e3, -0 and 12345678901234567890 come out exactly as they went in. Only deserializing into a .NET type converts them: JsonSerializer.Deserialize<double[]>("[1.50, 1e3]") serializes back as [1.5,1000].
Reader options
- AllowTrailingCommas accepts
[1, 2,]and{"a": 1,}. The comma is dropped from the output. - CommentHandling = Skip accepts
//and/* */comments and drops them.JsonDocumentcannot keep comments:JsonCommentHandling.Allowthrows anArgumentOutOfRangeException. - MaxDepth is 64 by default (0 means 64).
JsonSerializerOptions.MaxDepth, also 64, limits writing too, so the C# below sets both when you raise it. - AllowDuplicateProperties (.NET 10+) is true by default: both
"id"properties are kept and written. Set it to false to get an error instead.
To check whether the JSON is valid and find the error, use the JSON validator.
Format JSON in C#
The code follows the options above and gives the same output as this page:
using System.Text.Json;
static string FormatJson(string json)
{
// JsonDocument keeps numbers as written: 1.50, 1e3 and 12345678901234567890 stay as they are.
using var document = JsonDocument.Parse(json);
return JsonSerializer.Serialize(document.RootElement, new JsonSerializerOptions
{
WriteIndented = true,
NewLine = "\n", // .NET 9+; the default is Environment.NewLine
});
}
Tested on .NET 10. On .NET 8, leave out NewLine, IndentCharacter and IndentSize: the indentation is then always 2 spaces and the line break is Environment.NewLine.
FAQ
How do I pretty print JSON in C#?
Parse it with JsonDocument.Parse and write the root element with JsonSerializer.Serialize and WriteIndented = true, as in the code above. For your own objects, pass WriteIndented = true to the serializer directly.
Why does System.Text.Json write é and <?
The default encoder escapes every non-ASCII character and the HTML-sensitive characters, so the JSON is safe to embed in a page. Set Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping to keep them as they are.
Does formatting change my numbers?
Not with JsonDocument or JsonNode: they keep the number text, so 1.50, 1e3 and 12345678901234567890 are written as they were. Deserializing into double would give 1.5 and 1000.
Can it keep comments?
No. System.Text.Json can skip comments, but JsonDocument does not store them, so they are not in the output.
Why is my emoji still escaped with UnsafeRelaxedJsonEscaping?
The relaxed encoder only lets characters up to U+FFFF through, so emoji and other characters above it are written as a surrogate pair such as 😀. Every JSON parser reads that back as the same character.
The validator says my JSON is valid, but the formatter fails. Why?
A \u escape of half a surrogate pair, such as "\ud800", passes the reader, but JsonElement throws an InvalidOperationException when it reads the string to write it. JsonSerializer.Serialize reports it as "The object or value could not be serialized. Path: $."
Is my JSON uploaded?
No. The formatter runs entirely in your browser, so nothing gets uploaded.