Escape JSON in Go — json.Marshal, HTML Escape, and Encoder Options
- json.Marshal HTML-escapes <, >, and & by default — often not what you want for APIs.
- Use json.Encoder with SetEscapeHTML(false) to opt out of HTML escaping.
- json.Unmarshal handles the reverse correctly with no flags needed.
Table of Contents
Go's encoding/json package is the standard choice for JSON in Go. Like System.Text.Json in .NET and Gson in Java, it defaults to HTML-safe escaping — which surprises people whose JSON is going to APIs, not web pages. The fix is a flag on the encoder; the tricky part is knowing the flag exists.
json.Marshal — HTML-Escaped by Default
The simplest path is json.Marshal, which takes any value and returns JSON bytes:
import "encoding/json"
type User struct {
Name string
Bio string
}
u := User{Name: "Alex", Bio: "<b>hi</b> & bye"}
data, _ := json.Marshal(u)
// {"Name":"Alex","Bio":"\u003cb\u003ehi\u003c/b\u003e \u0026 bye"}
The <, >, and & get unicode-escaped as \u003c, \u003e, \u0026. Valid JSON, parses back correctly, but not what most developers expect.
json.Marshal is a convenience wrapper. For control over escape behavior, use json.Encoder directly.
json.Encoder With SetEscapeHTML(false)
Use json.NewEncoder and call SetEscapeHTML(false):
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.Encode(u)
// {"Name":"Alex","Bio":"<b>hi</b> & bye"}
// (with a trailing newline from Encode)
The encoder API is slightly more verbose but gives you control over HTML escaping, indentation (SetIndent), and a few other options json.Marshal doesn't expose.
One gotcha: Encode appends a newline. If you need exact bytes, trim it:
output := bytes.TrimSuffix(buf.Bytes(), []byte("\n"))
Sell Custom Apparel — We Handle Printing & Free Shipping
Struct Tags Control Output Field Names
Go capitalizes exported fields, but JSON usually wants lowercase or camelCase. Struct tags override the output name:
type User struct {
Name string `json:"name"`
Bio string `json:"bio,omitempty"`
}
The ,omitempty suffix skips the field in output if it's empty — useful for optional fields you don't want to send as empty strings.
Struct tags also support custom marshaling via interfaces — if you need non-default escape behavior for one field, implement MarshalJSON on that field's type.
json.Unmarshal Reads Escaped JSON Correctly
The reverse direction needs no special configuration:
data := []byte(`{"name":"Alex","bio":"She said \"hi\""}`)
var u User
json.Unmarshal(data, &u)
fmt.Println(u.Bio)
// She said "hi"
All escape sequences get unescaped correctly. Errors on unmarshal usually come from invalid JSON itself (unescaped quotes, bad escape sequences) rather than unescape issues.
For debugging escape-related errors in Go, pipe the input through a browser-based JSON formatter — it shows exactly where the parse fails.
Escaping a Single String for JSON
If you need the JSON-escaped form of a single Go string (not a struct), marshal the string itself:
s := `She said "hi"` escaped, _ := json.Marshal(s) fmt.Println(string(escaped)) // "She said \"hi\""
Returns the quoted string literal. Strip the outer quotes if you need just the inner content:
inner := string(escaped[1 : len(escaped)-1])
This is the safe way to escape arbitrary text for JSON in Go. Do not hand-roll string replacement — control characters and surrogate pairs are hard to handle correctly, and the library already does it right.
Escape JSON Outside Go
For log debugging and cross-stack JSON. Browser-based, no Go runtime.
Open Free JSON Escape / Unescape ToolFrequently Asked Questions
Why does json.Marshal escape < > and &?
HTML-safe encoding default. Use json.Encoder with SetEscapeHTML(false) to disable.
How do I marshal a map with non-string keys?
You cannot directly — JSON object keys must be strings. Convert the map to map[string]T or implement custom MarshalJSON.
How do I pretty-print JSON in Go?
json.MarshalIndent(data, "", " ") for two-space indent. Or enc.SetIndent("", " ") on an Encoder.
Why does my JSON have unexpected null values?
Unexported fields are skipped. Pointer fields that are nil become null. Use omitempty to skip empty values entirely.

