Blog post
JSON in Go: Working with encoding/json
Marshal and unmarshal JSON in Go — struct tags, custom marshalers, streaming, and common patterns with the encoding/json package.
Shashank Jain
Author


Article
Basic Marshal and Unmarshal
Go's encoding/json package is part of the standard library — no dependencies needed:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
// Marshal (Go struct to JSON)
user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
data, err := json.Marshal(user)
if err != nil { panic(err) }
fmt.Println(string(data))
// {"id":1,"name":"Alice","email":"alice@example.com"}
// Unmarshal (JSON to Go struct)
jsonStr := `{"id":2,"name":"Bob","email":"bob@example.com"}`
var decoded User
err = json.Unmarshal([]byte(jsonStr), &decoded)
if err != nil { panic(err) }
fmt.Println(decoded.Name) // Bob
}Struct Tags
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Description string `json:"description,omitempty"` // omit if zero value
internal string `json:"-"` // always omit
CreatedAt string `json:"created_at"`
}Dynamic JSON with map and interface{}
// Unknown structure
var result map[string]interface{}
json.Unmarshal([]byte(jsonStr), &result)
name, ok := result["name"].(string)
if ok {
fmt.Println(name)
}
// Any valid JSON
var raw interface{}
json.Unmarshal([]byte(jsonStr), &raw)Pretty Printing
data, _ := json.MarshalIndent(user, "", " ")
fmt.Println(string(data))Streaming Large JSON (Decoder)
import (
"encoding/json"
"os"
)
func streamJSON(filename string) {
f, _ := os.Open(filename)
defer f.Close()
decoder := json.NewDecoder(f)
// Read opening bracket
decoder.Token()
for decoder.More() {
var item map[string]interface{}
decoder.Decode(&item)
// Process item without loading full file
fmt.Println(item["id"])
}
}Custom Marshaler
type Color int
const (Red Color = iota; Green; Blue)
func (c Color) MarshalJSON() ([]byte, error) {
names := []string{"red", "green", "blue"}
if int(c) >= len(names) {
return nil, fmt.Errorf("unknown color: %d", c)
}
return json.Marshal(names[c])
}
func (c *Color) UnmarshalJSON(data []byte) error {
var name string
json.Unmarshal(data, &name)
names := map[string]Color{"red": Red, "green": Green, "blue": Blue}
*c = names[name]
return nil
}FAQ
Why does json.Unmarshal need a pointer?
Unmarshal needs to modify the variable you pass in. Go is pass-by-value, so without a pointer, modifications would affect a copy. Always pass &variable to Unmarshal.
How do I handle optional/nullable JSON fields in Go?
Use pointer types: *string, *int. A nil pointer marshals to null and a missing JSON field leaves the pointer nil on unmarshal.
What is json.RawMessage used for?
json.RawMessage lets you delay parsing of part of a JSON payload. Useful when different subtypes have different structures — unmarshal the discriminator field first, then parse the raw part based on its value.
Is encoding/json the fastest JSON library for Go?
No. Libraries like json-iterator, sonic, and go-json are 2-5x faster. For high-throughput services, consider replacing encoding/json with one of these drop-in alternatives.
How do I validate JSON in Go before unmarshaling?
Use json.Valid([]byte(data)) which returns a boolean without allocating. Or unmarshal into interface{} and inspect the result.
Keep reading
Recent blogs

Jun 14, 2026
JSON in C#: System.Text.Json and Newtonsoft Complete Guide
Serialize and deserialize JSON in C# using System.Text.Json and Newtonsoft.Json with practical examples.

Jun 14, 2026
JSON to Markdown Table: Convert JSON Arrays Instantly
Convert JSON arrays to Markdown tables in JavaScript, Python, and with the free online tool.

Jun 14, 2026
JSON in TypeScript: Type-Safe Parsing and Validation
Stop using any for JSON in TypeScript — use Zod, type guards, and generics for fully type-safe parsing.