How does encoding/json match a JSON key like "MAXCONNS" to a struct field named MaxConns?
answer
- two stages, not one
- exact first, then a fallback
- the fallback forgives only letters, not punctuation
- so a case variant is never unknown
- and two spellings land on one field, later wins
basics
~10 sencoding/json prefers an exact match on the field's name, and falls back to a match that ignores letter case. So MAXCONNS, maxconns and MaxCONNS all decode into MaxConns, while max_conns matches nothing.
solid answer
~40 sField matching in `encoding/json` is two-stage. For each JSON key the decoder first looks for a field whose name is exactly the key; if none exists it takes a field whose name differs only in letter case. That is why `{"MAXCONNS": 7}` and `{"maxconns": 7}` both populate a field declared as `MaxConns`, while `{"max_conns": 7}` populates nothing — the fallback forgives case, not punctuation, underscores or spelling. Two practical consequences follow. First, a key that is "wrong" only in capitalisation is accepted rather than reported, so strict decoding will not flag it: the key did match a field. Second, if one object carries both `"maxconns"` and `"MaxConns"`, both resolve to the same field and the later one in the document wins, because keys are applied in the order they appear.
code
go · 11 linestype Config struct {
MaxConns int
}
var a Config
json.Unmarshal([]byte(`{"MAXCONNS": 7}`), &a)
// a.MaxConns is 7
var b Config
json.Unmarshal([]byte(`{"max_conns": 7}`), &b)
// b.MaxConns is 0go deeper
Know that a JSON key does not have to match the Go field's capitalisation to be decoded into it, and that this is a property of the standard decoder rather than something you configure.
Explain the two-stage rule — exact match preferred, letter-case fallback second — and state precisely what the fallback does not forgive: underscores, separators and misspellings.
Use it in diagnosis: when an operator's setting did not take effect, a duplicate or case-variant key later in the document overwriting it is a cause you can rule in or out from the bytes.
Recognise that duplicate and case handling is per-decoder, so two consumers of the same document can act on different values. Decide whether that ambiguity is acceptable in your formats or must be forbidden at the producer.
## The matching rule When `encoding/json` decodes a JSON object into a struct it must decide, for each key in the document, which field of the struct receives it. The decoder builds the set of candidate names for the struct once (the field's declared name, or the name given for it in its struct tag when there is one) and then, per key: 1. **Exact match wins.** If some candidate name is byte-for-byte equal to the key, that field is used. 2. **Otherwise, letter case is ignored.** If exactly one candidate differs from the key only in the case of its letters, that field is used. 3. **Otherwise the key is unmatched** and its value is skipped. So for `type Config struct { MaxConns int }`, all of `"MaxConns"`, `"maxConns"`, `"maxconns"` and `"MAXCONNS"` decode into the field. None of `"max_conns"`, `"max conns"`, `"maxConnections"` or `"maxConnns"` do — the fallback is only about upper and lower case, never about separators, abbreviations or spelling. ## Why the fallback exists Go requires an exported field to start with a capital letter, while a great deal of JSON in the world is written in `camelCase` or `lowercase`. Without a fallback, every struct would need an explicit name for every field just to read ordinary documents. Case-insensitive matching makes the common case work with no ceremony — and it is a genuine convenience most of the time. ## The three surprises it produces **A miscapitalised key is not a typo to the decoder.** An operator who writes `"maxconns"` in a config file where the intended key is `"maxConns"` gets exactly the behaviour they wanted, silently. That is usually fine, but it means the file that works on your machine may not look like the file the documentation shows, and nothing will ever tell you. **Strict decoding does not catch case variants.** Turning on `DisallowUnknownFields` reports keys that match *no* field. A case variant matched a field, so it is not unknown and no error is produced. If you assume strict mode enforces the exact spelling of your keys, you are assuming something it does not do. **Two spellings in one object collapse onto one field.** An object may legally contain `"maxconns": 1` and `"MaxConns": 2`. Both resolve to the same struct field, and the decoder applies keys in document order, so the field ends up holding `2` — the later one wins, with no error and no sign that two values were offered. The same is true of an object that simply repeats a key: the last occurrence is the one that survives. ## Why last-wins matters beyond tidiness A config file assembled by concatenating fragments, or generated by a template that emits a section twice, can end up with the same setting expressed twice in slightly different case. The value that takes effect is the one further down the file, which is not necessarily the one the author edited. When an operator reports that their edit had no effect, a duplicate — exact or case-variant — further down the document is one of the two explanations worth checking first, alongside a key that matched nothing at all. It matters more when two different programs read the same document. Any consumer that resolves duplicates or letter case differently ends up acting on a different value than yours, from identical bytes. That divergence is invisible until the two systems disagree about something that matters. ## What to do about it - Do not rely on capitalisation as a correctness boundary; treat `maxconns` and `MaxConns` as the same setting, because your decoder does. - If duplicate settings are a real risk in your inputs, detect them yourself before or alongside the struct decode — the struct decode will never tell you, because by the time you hold the struct the loser has been overwritten. - Remember the asymmetry when you reason about strict decoding: it is a check on *unknown* names only. Case, duplicates and missing keys are all outside its remit.
- Does DisallowUnknownFields reject a key that differs from the field name only in case?No. That key matched a field through the case-insensitive fallback, so it is not an unknown field and no error is raised. Strict decoding only reports keys that match nothing at all — it is not a spelling or style check on the keys that do match.
- If one JSON object contains the same key twice, which value ends up in the struct?The last one. The decoder applies the object's keys in the order they appear, so a later occurrence overwrites an earlier one, and no error or warning marks the duplication. The same happens when two keys differ only in letter case, because both resolve to the same field.
- Why can this behaviour cause two services reading the same document to disagree?Because resolving duplicates and letter case is a choice each decoder makes. A consumer that takes the first occurrence, or that requires exact case, extracts a different value from identical bytes than one that takes the last and folds case. Nothing in the document declares which is right.
saying these in an interview costs you the question
- Says JSON key matching in Go is always exact
- Claims the fallback also ignores underscores or hyphens
- Thinks strict decoding enforces the exact capitalisation of keys
- Assumes a repeated key is a decode error
- Believes the first occurrence of a duplicate key wins