Blog
Wild & Free Tools

Escape JSON in Go — json.Marshal, HTML Escape, and Encoder Options

Last updated: January 2026 6 min read
Quick Answer

Table of Contents

  1. json.Marshal Defaults
  2. Disabling HTML Escape
  3. Struct Tags and Field Names
  4. json.Unmarshal Handles Unescape
  5. Escaping Individual Strings
  6. Frequently Asked Questions

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 Tool

Frequently 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.

Jake Morrison
Jake Morrison Security & Systems Engineer

Jake's conviction that files should never touch a third-party server is the foundation of WildandFree's zero-upload design.

More articles by Jake →
Launch Your Own Clothing Brand — No Inventory, No Risk