Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Go Language JSON Formatting

Formatting JSON data is a common task in backend development. It involves taking structured JSON data, often represented as Go structs or maps, and converting it into a string format that adheres to the JSON specification. While simply converting to a JSON string is easy,formatting typically implies adding indentation and newlines to make the output human-readable. Go's standard library provides robust tools for this.

Why Format JSON?

Even though machines don't strictly need formatted JSON (parsers can handle compact JSON), formatting is crucial for:

  • Readability: Makes inspecting JSON data in logs, debugging tools, or API responses much easier.
  • Debugging: Quickly spot structural issues or incorrect values in complex JSON structures.
  • Consistency: Provides a standard way to present JSON output from your application.

The Standard Library: encoding/json

Go's built-in encoding/json package is the primary tool for working with JSON. It provides functions for both *marshaling* (Go data to JSON) and *unmarshaling* (JSON to Go data). For formatting, we primarily use the marshaling capabilities.

Basic Marshaling vs. Formatted Marshaling

The json.Marshal function converts a Go value into a compact JSON byte slice.

Basic Marshal Example:

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	Name string
	Age  int
}

func main() {
	user := User{"Alice", 30}

	// Marshal the struct into a JSON byte slice
	jsonData, err := json.Marshal(user)
	if err != nil {
		fmt.Println("Error marshaling JSON:", err)
		return
	}

	// jsonData will be []byte{'{"Name":"Alice","Age":30}'}
	fmt.Println(string(jsonData))
}

Output: {"Name":"Alice","Age":30}

To get a human-readable, indented output, you use the json.MarshalIndent function. It takes the Go value and two additional string arguments: a prefix and an indent string.

Using json.MarshalIndent

MarshalIndent signature:

func MarshalIndent(v any, prefix, indent string) ([]byte, error)

  • v any: The Go value to encode.
  • prefix string: A string prepended to each line of the output. Commonly an empty string "".
  • indent string: A string used for one level of indentation. Commonly "\t" (tab) or " " (two spaces).

Example with MarshalIndent

Using Two Spaces for Indentation:

package main

import (
	"encoding/json"
	"fmt"
)

type Product struct {
	ID       int      `json:"id"`
	Name     string   `json:"name"`
	Price    float64  `json:"price"`
	Tags     []string `json:"tags,omitempty"` // omitempty means omit if slice is empty
	IsInStock bool    `json:"isInStock,string"` // marshal bool as string "true" or "false"
}

func main() {
	product := Product{
		ID: 101,
		Name: "Go T-Shirt",
		Price: 25.99,
		Tags: []string{"clothing", "go", "programming"},
		IsInStock: true,
	}

	// Marshal with empty prefix and two spaces for indent
	jsonData, err := json.MarshalIndent(product, "", "  ")
	if err != nil {
		fmt.Println("Error marshaling JSON:", err)
		return
	}

	// Output the formatted JSON string
	fmt.Println(string(jsonData))
}

Expected Output:

{
  "id": 101,
  "name": "Go T-Shirt",
  "price": 25.99,
  "tags": [
    "clothing",
    "go",
    "programming"
  ],
  "isInStock": "true"
}

Using Tabs for Indentation:

// ... using the same Product struct and value as above ...

func main() {
	product := Product{ /* ... values ... */ }

	// Marshal with empty prefix and a tab for indent
	jsonData, err := json.MarshalIndent(product, "", "\t") // Use "\t" for a tab
	if err != nil {
		fmt.Println("Error marshaling JSON:", err)
		return
		}

	fmt.Println(string(jsonData))
}

Expected Output:

{
	"id": 101,
	"name": "Go T-Shirt",
	"price": 25.99,
	"tags": [
		"clothing",
		"go",
		"programming"
	],
	"isInStock": "true"
}

Using a Prefix:

The prefix string is added to the beginning of *every* line, including the first and the closing brace/bracket. This is less common for standard JSON formatting but can be useful for log messages or embedding JSON within other text formats.

// ... using the same Product struct and value as above ...

func main() {
	product := Product{ /* ... values ... */ }

	// Marshal with prefix "LOG: " and two spaces for indent
	jsonData, err := json.MarshalIndent(product, "LOG: ", "  ")
	if err != nil {
		fmt.Println("Error marshaling JSON:", err)
		return
	}

	fmt.Println(string(jsonData))
}

Expected Output:

LOG: {
LOG:   "id": 101,
LOG:   "name": "Go T-Shirt",
LOG:   "price": 25.99,
LOG:   "tags": [
LOG:     "clothing",
LOG:     "go",
LOG:     "programming"
LOG:   ],
LOG:   "isInStock": "true"
LOG: }

Handling Different Data Types

json.MarshalIndent works seamlessly with various Go data types that can be encoded to JSON:

  • Structs: Fields are encoded based on their exported names or `json` struct tags.
  • Maps: Maps with string keys are encoded as JSON objects.
  • Slices/Arrays: Encoded as JSON arrays.
  • Primitive Types: Numbers, strings, booleans, null (for `nil` pointers/interfaces) are encoded directly.

Struct tags (`json:"..."`) are crucial for controlling how fields are named, omitted (`omitempty`), or encoded (`string`).

Streaming JSON Encoding

For very large JSON objects or arrays, marshaling the entire structure into memory using Marshal or MarshalIndent might consume excessive memory. In such cases, streaming encoders are preferred.

The json.NewEncoder function creates an encoder that writes directly to an io.Writer (like os.Stdout, an http.ResponseWriter, or a file).

Streaming Encoder Example:

package main

import (
	"encoding/json"
	"os" // We'll write to standard output
)

type Item struct {
	Name  string `json:"name"`
	Value int    `json:"value"`
}

func main() {
	items := []Item{
		{Name: "Apple", Value: 1},
		{Name: "Banana", Value: 2},
		{Name: "Cherry", Value: 3},
	}

	// Create a new encoder that writes to os.Stdout
	encoder := json.NewEncoder(os.Stdout)

	// To get indented output with the encoder, use SetIndent
	encoder.SetIndent("", "  ") // Prefix "", Indent "  "

	// Encode the slice of items. It will be written directly to os.Stdout, formatted.
	err := encoder.Encode(items)
	if err != nil {
		// Handle error
		return
	}

	// The output is streamed to os.Stdout, formatted.
}

Expected Output (to Standard Output):

[
  {
    "name": "Apple",
    "value": 1
  },
  {
    "name": "Banana",
    "value": 2
  },
  {
    "name": "Cherry",
    "value": 3
  }
]

Using json.NewEncoder with SetIndent is the most efficient way to output formatted JSON directly to a stream or response writer without buffering the entire formatted string in memory first.

Conclusion

Go's encoding/json package provides simple yet powerful functions for formatting JSON output. For most common use cases, json.MarshalIndent with appropriate prefixand indent strings is sufficient. For scenarios involving large datasets or direct output to network connections/files, the streaming approach with json.NewEncoderand SetIndent offers a more memory-efficient solution. Understanding these standard library tools is fundamental for effective JSON handling in Go applications.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool