Standard library patterns · 3 MIN READ

Struct tags are the JSON schema

Control field names, omit empties, and know what unmarshalling leaves untouched.

encoding/json maps struct fields by name, and a tag overrides that. Only exported fields are considered, which is the first thing that catches people: a lower-case field is invisible to the encoder with no error.

Unmarshal leaves fields absent from the input untouched rather than zeroing them, so decoding into a reused struct can silently keep old values.

SEE THE IDEA

Exported fields and tags define the wire representation

  1. 01

    Go value

    go
    type Config struct { Name string }

    Only exported fields are considered

  2. 02

    Field tag

    go
    `json:"name,omitempty"`

    Controls naming and omission

  3. 03

    JSON bytes

    go
    json.Marshal(config)

    Encoding can return an error

Decoding into an existing value can preserve fields absent from the input.

In practice

Treat tags as part of the external contract and test representative encoded values. Decode into a fresh struct when missing fields should become zero. Use a pointer field when an absent field must be distinguished from a present zero, and consider DisallowUnknownFields for strict input.

Watch out

Do not silently ignore Marshal or Unmarshal errors. Lowercase fields remain invisible even if they carry a json tag.

Official reference ↗

Read and predict

go
package main

import (
	"encoding/json"
	"fmt"
)

type Config struct {
	Host    string `json:"host"`
	Port    int    `json:"port,omitempty"`
	secret  string
}

func main() {
	data, _ := json.Marshal(Config{Host: "localhost", secret: "hidden"})
	fmt.Println(string(data))
}

What is printed, and where did port and secret go?

Try it yourself

Trace each value and write down your reasoning. Copy the source into GopherForge or your local Go environment to compile and check it. This web page does not execute your code or send it to a compiler service.

Progress stays in this browser. No account needed.