What does json.Decoder.DisallowUnknownFields change about a Decode call that reads an unexpected key?
answer
- a switch on the Decoder, set before Decode
- the error text names the key
- not available on Unmarshal at all
- the first offender only, and the struct is already partly filled
- silent on duplicates, case variants and missing keys
basics
~20 sIt makes a key that matches no field of the destination struct an error instead of a silent skip. Decode then returns an error naming the first such key, for example: json: unknown field "maxConnnections".
solid answer
~50 s`DisallowUnknownFields()` is a switch on a `*json.Decoder`, set before you call `Decode`. With it on, a JSON object key that matches no field of the destination is no longer skipped: the decode reports an error whose text names it, such as `json: unknown field "maxConnnections"`. Three details matter in practice. It exists only on the `Decoder` — `json.Unmarshal` has no equivalent, so with bytes in hand you write `json.NewDecoder(bytes.NewReader(b))` and set the flag on that. The decoder keeps going after the offending key, so it reports the first unknown field rather than a list of all of them, and the destination may already hold the fields that did match — treat the error as fatal and discard the value. And its scope is narrow: it catches unmatched names only, not duplicate keys, not case variants that matched a field, and not required keys that are missing.
code
go · 8 linesdec := json.NewDecoder(f)
dec.DisallowUnknownFields()
var c Config
if err := dec.Decode(&c); err != nil {
// json: unknown field "maxConnnections"
return fmt.Errorf("config: %w", err)
}go deeper
Remember that strict decoding is opt-in, that it lives on a json.Decoder rather than on Unmarshal, and that the resulting error message tells you which key was not understood.
Be precise about the boundaries: it reports the first unmatched key, decoding continues, and it says nothing about duplicates, case variants or missing keys. Show the bytes.NewReader wrapping for Unmarshal-shaped code.
Place the check where failing is cheap — CI validation and process startup — and explain what a long-running process should do when a hot reload fails it, rather than leaving the answer at turn it on everywhere.
Weigh the cost of rejection: strict decoding blocks a rollout on any key a newer writer added, so decide who owns that failure and what the documented, observable escape hatch is.
## What the option does `json.Decoder` is the streaming decoder you get from `json.NewDecoder(r)` over any `io.Reader`. Calling `DisallowUnknownFields()` on it, before `Decode`, changes exactly one behaviour: an object key that matches no field of the destination struct stops being skipped and becomes a decode error instead. ```go dec := json.NewDecoder(f) dec.DisallowUnknownFields() var c Config if err := dec.Decode(&c); err != nil { return fmt.Errorf("config: %w", err) } ``` The error is an ordinary error value, not a dedicated exported type, and its text carries the key name: `json: unknown field "maxConnnections"`. That name is the whole value of the feature — it turns "my setting had no effect" into a message that says which key nobody understood. ## The three details that catch people out **It lives on the Decoder, not on Unmarshal.** There is no `json.UnmarshalStrict`, and no option argument on `json.Unmarshal`. When what you have is a `[]byte` — a request body already read, a file already loaded — you wrap it: `json.NewDecoder(bytes.NewReader(b))`, set the flag, then `Decode`. That is the standard way to get strict behaviour on bytes. **Decoding continues, and only the first unknown key is named.** The decoder records the problem and carries on through the rest of the document, so a file with four unrecognised keys yields one error mentioning one of them. Fix it, rerun, and the next one appears. Just as importantly, the destination struct is not left untouched: the keys that did match have already been assigned. A struct populated by a decode that returned an error is not a partially-valid configuration to fall back on — discard it and keep whatever you were using before. **Its scope is narrower than "strict".** Enabling it does not make the decode strict in general. It says nothing about: - *Duplicate keys.* An object containing the same key twice is accepted; the later value overwrites the earlier one and no error is raised. - *Case variants.* A key that differs from the field name only in letter case matched a field, so it is not unknown and passes. - *Missing keys.* There is no notion of a required field; an absent key leaves the Go zero value, which the decoder considers a perfectly good outcome. - *Extra content after the value.* `Decode` reads one JSON value from the stream; whatever follows it is simply not read by that call. If you describe the option to a colleague as "strict JSON", they will assume all four of those are covered. Describe it as what it is: an unknown-key check. ## Where to turn it on The natural home is a boundary you control and can fail at cheaply. Loading a config file at process startup is the strongest case: a typo becomes a startup failure with the key printed, before the process takes any traffic, and the operator gets an unambiguous message instead of a mystery. Validating a rendered config in CI is stronger still, because nothing is deployed at all. An HTTP handler decoding a request body is a more mixed case. Rejecting a request because the client sent a field you do not know is a contract decision, not a hygiene one — it makes your API brittle to any client that adds a field before you do. Many teams run strict decoding on internal, self-generated documents and lenient decoding on anything a third party sends. ## Getting the report without the failure The option has no warn-only mode: it either errors or it does not. When you want the name of the offending key but cannot afford to fail, decode twice from the same bytes — once leniently into your struct, which is the value you use, and once strictly, purely to see what it complains about, logging the key. The second decode costs one more pass over a small document and converts an invisible failure into a log line an operator can act on.
- How do you get the same strictness when you only have a []byte and not a stream?Wrap the bytes in a reader: `json.NewDecoder(bytes.NewReader(b))`, call `DisallowUnknownFields()` on the decoder, then `Decode`. `json.Unmarshal` offers no strict variant, so this wrapping is the idiomatic way to apply the check to a request body or a file you have already read into memory.
- With the option enabled, what happens if the same key appears twice in one object?Nothing is reported. The key matches a field, so it is not unknown; the decoder assigns each occurrence in order and the last value wins. Unknown-key checking and duplicate-key checking are separate concerns, and `encoding/json` gives you only the first one.
- Can you trust the destination struct after Decode returns an unknown-field error?No. Decoding continues past the offending key, so the struct already holds every field that did match — a partial configuration that was never validated as a whole. Return the error, discard the value, and for a running process keep the configuration it was already using.
- Would you enable it on an HTTP handler decoding a client's request body?Only deliberately. It turns any field a client adds into a rejected request, which is fine for a body your own code produces and harsh for a third-party client that ships ahead of you. A common split is strict on internal, self-generated documents and lenient plus logging on external input.
saying these in an interview costs you the question
- Thinks json.Unmarshal takes a strict option too
- Says the error lists every unknown key in the document
- Assumes the struct is untouched when the strict decode errors
- Calls it strict JSON and expects duplicate keys rejected
- Believes it also enforces that required keys are present
- Sets the flag after Decode has already run