Skip to content
Go Basics: Reject Extra JSON Values in Configuration

Go Basics: Reject Extra JSON Values in Configuration

October 31, 2026

One successful Decode call does not prove that a configuration stream contains exactly one JSON value. Check the remainder before accepting the parsed configuration.

Prepare a scratch directory

Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as json-single-document-demo.go:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"
)

type Config struct {
	Port int `json:"port"`
}

func parse(input string) (Config, error) {
	var cfg Config
	decoder := json.NewDecoder(strings.NewReader(input))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&cfg); err != nil {
		return cfg, err
	}
	var extra any
	if err := decoder.Decode(&extra); err != io.EOF {
		if err != nil {
			return cfg, err
		}
		return cfg, fmt.Errorf("extra JSON value")
	}
	if cfg.Port < 1 || cfg.Port > 65535 {
		return cfg, fmt.Errorf("invalid port")
	}
	return cfg, nil
}
func main() {
	for _, input := range []string{`{"port":8080}`, `{"port":8080,"debug":true}`, `{"port":8080} {}`, `null`} {
		cfg, err := parse(input)
		fmt.Printf("%s => accepted=%t\n", input, err == nil)
		if err == nil {
			fmt.Println("Port:", cfg.Port)
		}
	}
}

Run it:

go run json-single-document-demo.go

Observed output:

{"port":8080} => accepted=true
Port: 8080
{"port":8080,"debug":true} => accepted=false
{"port":8080} {} => accepted=false
null => accepted=false

Read the result

The second decode must report io.EOF; whitespace alone is allowed, but another value is rejected. Unknown fields fail on the first decode. The range check rejects both a missing port and the zero-valued result of decoding null.

Apply it carefully

This is not a complete strict-JSON policy. encoding/json accepts duplicate object keys and performs case-insensitive struct-field matching. Bound untrusted input before decoding and add separate policy if exact key spelling or duplicate-key rejection matters. Do not use cfg when parse returns an error.

The official Go reference documents the behavior used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.

rm -- json-single-document-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on