json.MarshalIndent(v, "", " ") encodes a Go value as JSON with each element on its own line, indented two spaces per level. The second argument is a prefix for every new line, usually empty; the third is one level of indent.
type User struct {
Name string `json:"name"`
ID uint64 `json:"id"`
Tags []string `json:"tags"`
Active bool `json:"active"`
Manager *string `json:"manager"`
}
u := User{Name: "café", ID: 12345678901234567890, Tags: []string{"api", "ops"}, Active: true}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
{
"name": "café",
"id": 12345678901234567890,
"tags": [
"api",
"ops"
],
"active": true,
"manager": null
}
Struct fields come out in declaration order, and the nil pointer is null. The formatter below prints the same for that JSON text with 2 spaces.
JSON you already have: json.Indent
For bytes from a file or an HTTP body, don’t decode and re-encode. json.Indent reformats the bytes themselves, so every number and string escape stays exactly as written, and it returns an error for invalid input:
var buf bytes.Buffer
if err := json.Indent(&buf, body, "", " "); err != nil {
log.Fatal(err) // for {"port":8080,}: invalid character '}' looking for beginning of object key string
}
fmt.Println(buf.String())
Run on the text above, it prints the same output. json.Compact minifies the same way, and json.Valid only checks.
Decoding into any loses things
var v any
json.Unmarshal([]byte(`{"price":1.10,"id":12345678901234567890,"exp":1e5,"b":1,"a":2}`), &v)
out, _ := json.MarshalIndent(v, "", " ")
fmt.Println(string(out))
{
"a": 2,
"b": 1,
"exp": 100000,
"id": 12345678901234567000,
"price": 1.1
}
Every number becomes a float64, exact only up to 2^53, and a map’s keys are written sorted. A number too big for a float64, such as 1e1000, makes Unmarshal fail with json: cannot unmarshal number 1e1000 into Go value of type float64. A Decoder with UseNumber keeps each number’s text as a json.Number:
dec := json.NewDecoder(bytes.NewReader(body))
dec.UseNumber()
var v any
if err := dec.Decode(&v); err != nil {
log.Fatal(err)
}
Re-encoded, that gives "exp": 1e5, "id": 12345678901234567890 and "price": 1.10, with the keys still sorted: what the formatter below shows with Sort keys on.
HTML escaping
Marshal and MarshalIndent escape <, > and & so the output is safe inside an HTML <script> tag: "<b>Tom & Jerry</b>" comes out as "\u003cb\u003eTom \u0026 Jerry\u003c/b\u003e". To keep them readable, use an Encoder:
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
enc.Encode(map[string]string{"html": "<b>Tom & Jerry</b>"})
{
"html": "<b>Tom & Jerry</b>"
}
Encode ends the output with a newline, which MarshalIndent doesn’t. json.Indent never escapes: it copies strings as they are.