Skip to content

Output Format

m4bwav edited this page Sep 30, 2026 · 2 revisions

What PrettyPrintJson() writes, rule by rule, with the default options. Every rule is pinned by a test in the repository, and a change to any of them is a changelog entry.

The rules

  1. Whitespace outside strings (space, tab, carriage return, line feed) is discarded. The input's layout, line endings and indentation do not matter.
  2. After { and [: a line break, one level deeper.
  3. After ,: a line break at the current level.
  4. Before } and ]: one level back and a line break, unless the scope is empty.
  5. An empty object or array prints as {} or [] on the line where it opened, whatever whitespace was inside it ({ } and [<TAB>] too).
  6. After :: one space. Nothing before it.
  7. Everything else is copied through: string contents with their escapes, numbers, true, false, null, and any character the printer does not recognise.
  8. A line break is NewLine ("\n" by default) followed by IndentSize spaces (four by default) or one tab per open scope.
  9. No trailing line break: the text ends with the document's last character.

So

{ "a" : 1 , "b" : [ 1 , 2 ] }

becomes

{
    "a": 1,
    "b": [
        1,
        2
    ]
}

and empty scopes at any depth stay on their line:

{"a":{"b":[],"c":{ }},"d":[[],{},[ { } ]]}
{
    "a": {
        "b": [],
        "c": {}
    },
    "d": [
        [],
        {},
        [
            {}
        ]
    ]
}

What the printer never changes

  • Key order and duplicate keys. {"z":1,"a":2,"z":3} comes out with the three keys in that order.
  • Numbers. 1.0, 1e3, -0 and 007 are copied as written; nothing is parsed or normalised.
  • Strings. Escapes stay escapes: \u00e9 is not turned into é, and \n is not turned into a line break. Characters outside ASCII stay as they are. Whitespace inside a string is kept, tabs included.
  • A top-level scalar. 42 or "hi there" prints as itself.
  • A byte order mark at the start of the text is copied through as the first character. File.ReadAllText strips it before you see it; a raw byte-to-string conversion may not.

Idempotence

Pretty printing the output again gives the same text, because rule 1 discards the layout the first pass added. That makes PrettyPrintJson() a normaliser for comparing two documents that differ only in whitespace; Recipes shows the pattern and its limit.

Line endings and other layouts

The default is a line feed on every operating system, since 3.0. Windows tools that want CRLF get it with new JsonPrettyPrintOptions { NewLine = Environment.NewLine } or NewLine = "\r\n". NewLine can be any string, and IndentSize can be zero, which gives two more layouts:

  • NewLine = " " with IndentSize = 0 turns {"a":1,"b":[1,2],"c":{}} into the single readable line { "a": 1, "b": [ 1, 2 ], "c": {} }.
  • NewLine = "" with IndentSize = 0 turns {"a":1,"b":[1,2]} into {"a": 1,"b": [1,2]}: only the space after each colon is added.

Compared with System.Text.Json

JsonSerializer.Serialize(node, new JsonSerializerOptions { WriteIndented = true }) parses the text and writes it out again: two spaces per level, Environment.NewLine, escapes rewritten, and a JsonException for anything that is not strict JSON. This library indents the text you have without parsing it: four spaces, a line feed, everything else untouched, and non-standard input goes through. Not a validator is the other side of that coin.

Clone this wiki locally