Summary
When a generated Unmarshal{JSON,YAML} is called with a top-level null document, the required-field check is silently skipped and an all-zero struct is returned with no error. Any schema with required is affected.
Reproducer
Using the existing in-tree fixture tests/data/validation/requiredFields/requiredFields.json:
package main
import (
"encoding/json"
"fmt"
test "github.com/atombender/go-jsonschema/tests/data/validation/requiredFields"
)
func main() {
var r test.RequiredFields
err := json.Unmarshal([]byte(`null`), &r)
fmt.Printf("err = %v, MyString = %v\n", err, r.MyString)
}
Output:
err = <nil>, MyString = <nil>
Expected: a field myString in RequiredFields: required error.
Root cause
pkg/generator/validator.go, requiredValidator.generate:
out.Printlnf(`if _, ok := %s["%s"]; %s != nil && !ok {`, varNameRawMap, v.jsonName, varNameRawMap)
When the input is JSON null, json.Unmarshal(value, &raw) succeeds and leaves raw == nil. The raw != nil && short-circuit makes the required check skip entirely. The same pattern exists in readOnlyValidator.generate and the additional-properties strict path.
This guard was added in commit 8407170 (#215, "Removes nil check for required properties") to fix #97 — JSON Schema §6.5.3 says required checks presence, not non-nullness, so {"x": null} should be a valid required:["x"] payload. The fix correctly handled that case but inadvertently also lets the entire document be null.
Why this is still wrong
The inline comment in validator.go reads:
The container itself may be null (if the type is ["null", "object"]), in which case the map will be nil and none of the properties are present. This shouldn't fail the validation, though, as that's allowed as long as the container is allowed to be null.
The behavior matches that comment only when the container's type list includes null. For pure "object" schemas — the common case — a top-level null document should be rejected, but currently isn't.
Possible fixes
-
Type-aware guard. Only short-circuit when the container schema's type list includes "null". The required validator would need access to the parent schema's type info; threading it through is straightforward (requiredValidator already gets declName). Most precise; matches the comment's stated intent.
-
Top-level null rejection. Add a if raw == nil { return fmt.Errorf("...: cannot be null") } block before the per-property checks, gated similarly. Simpler but slightly different error shape.
-
Caller-side guard. Reject null in generateUnmarshalBody (unmarshal_body.go) for non-nullable container types, before any validator runs. Keeps requiredValidator itself unchanged.
Blast radius
A behavior change here regenerates roughly every golden file with a required: schema (~50–80 files in tests/data/). Mostly mechanical (the raw != nil && literal disappears or becomes type-conditional), but worth surfacing up front.
References
Summary
When a generated
Unmarshal{JSON,YAML}is called with a top-levelnulldocument, therequired-field check is silently skipped and an all-zero struct is returned with no error. Any schema withrequiredis affected.Reproducer
Using the existing in-tree fixture
tests/data/validation/requiredFields/requiredFields.json:Output:
Expected: a
field myString in RequiredFields: requirederror.Root cause
pkg/generator/validator.go,requiredValidator.generate:When the input is JSON
null,json.Unmarshal(value, &raw)succeeds and leavesraw == nil. Theraw != nil &&short-circuit makes the required check skip entirely. The same pattern exists inreadOnlyValidator.generateand the additional-properties strict path.This guard was added in commit
8407170(#215, "Removes nil check forrequiredproperties") to fix #97 — JSON Schema §6.5.3 saysrequiredchecks presence, not non-nullness, so{"x": null}should be a validrequired:["x"]payload. The fix correctly handled that case but inadvertently also lets the entire document benull.Why this is still wrong
The inline comment in
validator.goreads:The behavior matches that comment only when the container's type list includes
null. For pure"object"schemas — the common case — a top-level null document should be rejected, but currently isn't.Possible fixes
Type-aware guard. Only short-circuit when the container schema's type list includes
"null". The required validator would need access to the parent schema's type info; threading it through is straightforward (requiredValidatoralready getsdeclName). Most precise; matches the comment's stated intent.Top-level null rejection. Add a
if raw == nil { return fmt.Errorf("...: cannot be null") }block before the per-property checks, gated similarly. Simpler but slightly different error shape.Caller-side guard. Reject
nullingenerateUnmarshalBody(unmarshal_body.go) for non-nullable container types, before any validator runs. KeepsrequiredValidatoritself unchanged.Blast radius
A behavior change here regenerates roughly every golden file with a
required:schema (~50–80 files intests/data/). Mostly mechanical (theraw != nil &&literal disappears or becomes type-conditional), but worth surfacing up front.References
requiredproperties in objects #97 — original spec-compliance issuerequiredproperties #215 — PR that introduced the current short-circuit