From 12883f9dc0124dc0d1c1383967db3a5cc3027a8c Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Mon, 10 Nov 2025 14:19:48 -0300 Subject: [PATCH 1/4] fix: infer - arrays should always accept null When building the JSON schema for an array we should always allow "null" since any slice in Go can contain a `nil` value. Resolves #48 --- jsonschema/infer.go | 2 +- jsonschema/infer_test.go | 3 ++- jsonschema/validate_test.go | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/jsonschema/infer.go b/jsonschema/infer.go index 5b40b11..a60a001 100644 --- a/jsonschema/infer.go +++ b/jsonschema/infer.go @@ -216,7 +216,7 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas ma } case reflect.Slice, reflect.Array: - s.Type = "array" + s.Types = []string{"null", "array"} itemsSchema, err := forType(t.Elem(), seen, ignore, schemas) if err != nil { return nil, fmt.Errorf("computing element schema: %v", err) diff --git a/jsonschema/infer_test.go b/jsonschema/infer_test.go index 64a18f4..57e0ee0 100644 --- a/jsonschema/infer_test.go +++ b/jsonschema/infer_test.go @@ -96,6 +96,7 @@ func TestFor(t *testing.T) { {"level", forType[slog.Level](ignore), &schema{Type: "string"}}, {"bigint", forType[big.Int](ignore), &schema{Type: "string"}}, {"bigint", forType[*big.Int](ignore), &schema{Types: []string{"null", "string"}}}, + {"int64array", forType[[]int64](ignore), &schema{Types: []string{"null", "array"}, Items: &schema{Type: "integer"}}}, {"custom", forType[custom](ignore), &schema{Type: "custom"}}, {"intmap", forType[map[string]int](ignore), &schema{ Type: "object", @@ -125,7 +126,7 @@ func TestFor(t *testing.T) { Type: "object", Properties: map[string]*schema{ "f": {Type: "integer", Description: "fdesc"}, - "G": {Type: "array", Items: &schema{Type: "number"}}, + "G": {Types: []string{"null", "array"}, Items: &schema{Type: "number"}}, "P": {Types: []string{"null", "boolean"}, Description: "pdesc"}, "PT": {Types: []string{"null", "string"}}, "NoSkip": {Type: "string"}, diff --git a/jsonschema/validate_test.go b/jsonschema/validate_test.go index 30e6a94..bcdd239 100644 --- a/jsonschema/validate_test.go +++ b/jsonschema/validate_test.go @@ -512,7 +512,7 @@ func TestStructEmbedding(t *testing.T) { name: "ExportedPointer", targetType: reflect.TypeOf([]Banana{}), wantSchema: &Schema{ - Type: "array", + Types: []string{"null", "array"}, Items: &Schema{ Type: "object", Properties: map[string]*Schema{ @@ -532,7 +532,7 @@ func TestStructEmbedding(t *testing.T) { name: "UnExportedPointer", targetType: reflect.TypeOf([]Durian{}), wantSchema: &Schema{ - Type: "array", + Types: []string{"null", "array"}, Items: &Schema{ Type: "object", Properties: map[string]*Schema{ @@ -552,7 +552,7 @@ func TestStructEmbedding(t *testing.T) { name: "ExportedValue", targetType: reflect.TypeOf([]Fig{}), wantSchema: &Schema{ - Type: "array", + Types: []string{"null", "array"}, Items: &Schema{ Type: "object", Properties: map[string]*Schema{ @@ -572,7 +572,7 @@ func TestStructEmbedding(t *testing.T) { name: "UnExportedValue", targetType: reflect.TypeOf([]Honeyberry{}), wantSchema: &Schema{ - Type: "array", + Types: []string{"null", "array"}, Items: &Schema{ Type: "object", Properties: map[string]*Schema{ From 4ae7eb31d0c012781ce4fab241aaf59e2a1aad31 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Mon, 10 Nov 2025 14:28:54 -0300 Subject: [PATCH 2/4] Restrict changes to slices and not arrays --- jsonschema/infer.go | 6 +++++- jsonschema/infer_test.go | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/jsonschema/infer.go b/jsonschema/infer.go index a60a001..cb6c89a 100644 --- a/jsonschema/infer.go +++ b/jsonschema/infer.go @@ -216,7 +216,11 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas ma } case reflect.Slice, reflect.Array: - s.Types = []string{"null", "array"} + if t.Kind() == reflect.Slice { + s.Types = []string{"null", "array"} + } else { + s.Type = "array" + } itemsSchema, err := forType(t.Elem(), seen, ignore, schemas) if err != nil { return nil, fmt.Errorf("computing element schema: %v", err) diff --git a/jsonschema/infer_test.go b/jsonschema/infer_test.go index 57e0ee0..f6e82d8 100644 --- a/jsonschema/infer_test.go +++ b/jsonschema/infer_test.go @@ -96,7 +96,8 @@ func TestFor(t *testing.T) { {"level", forType[slog.Level](ignore), &schema{Type: "string"}}, {"bigint", forType[big.Int](ignore), &schema{Type: "string"}}, {"bigint", forType[*big.Int](ignore), &schema{Types: []string{"null", "string"}}}, - {"int64array", forType[[]int64](ignore), &schema{Types: []string{"null", "array"}, Items: &schema{Type: "integer"}}}, + {"int64slice", forType[[]int64](ignore), &schema{Types: []string{"null", "array"}, Items: &schema{Type: "integer"}}}, + {"int64array", forType[[2]int64](ignore), &schema{Type: "array", Items: &schema{Type: "integer"}, MinItems: jsonschema.Ptr(2), MaxItems: jsonschema.Ptr(2)}}, {"custom", forType[custom](ignore), &schema{Type: "custom"}}, {"intmap", forType[map[string]int](ignore), &schema{ Type: "object", From 75258da5313efca0d13d06f9ec0067cc0143f300 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Wed, 19 Nov 2025 17:03:41 -0300 Subject: [PATCH 3/4] Lock behaviour change behind environment variable and add documentation Following suggestions from @jba https://github.com/google/jsonschema-go/pull/49#issuecomment-3553439768 --- jsonschema/doc.go | 14 ++++++++++++++ jsonschema/infer.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/jsonschema/doc.go b/jsonschema/doc.go index c9ff364..8475743 100644 --- a/jsonschema/doc.go +++ b/jsonschema/doc.go @@ -91,6 +91,20 @@ implementations. See [learnjsonschema.com] for more recommendations about "forma The content keywords described in [section 8 of the validation spec] are recorded in the schema, but ignored during validation. +# Controlling behavior changes + +Minor and patch releases of this package may introduce behavior changes as part +of bug fixes or correctness improvements. To help manage the impact of such +changes, the package allows you to lock in specific behaviors using the +`JSONSCHEMAGODEBUG` environment variable. The available settings are listed +below; additional options may be introduced in future releases. + +- **typeschemasnull**: When set to `"1"`, the inferred schema for slices will +*not* include the `null` type alongside the array type. It will also avoid +adding `null` to non-native pointer types (such as `time.Time`). This restores +the behavior from versions prior to v0.3.0. The default behavior is to include +`null` in these cases. + [JSON Schema specification]: https://json-schema.org [section 7 of the validation spec]: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7 [section 8 of the validation spec]: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.8 diff --git a/jsonschema/infer.go b/jsonschema/infer.go index cb6c89a..1f46294 100644 --- a/jsonschema/infer.go +++ b/jsonschema/infer.go @@ -216,7 +216,7 @@ func forType(t reflect.Type, seen map[reflect.Type]bool, ignore bool, schemas ma } case reflect.Slice, reflect.Array: - if t.Kind() == reflect.Slice { + if os.Getenv(debugEnv) != "typeschemasnull=1" && t.Kind() == reflect.Slice { s.Types = []string{"null", "array"} } else { s.Type = "array" From c6aa6b102d4c85261189bb5a5a4e426ed09b43c4 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Wed, 26 Nov 2025 19:11:14 -0300 Subject: [PATCH 4/4] Apply @jba feedback * Improve documentation [1] * Improve test coverage [2] [1] https://github.com/google/jsonschema-go/pull/49/files#r2566502953 [2] https://github.com/google/jsonschema-go/pull/49/files#r2542591518 --- jsonschema/doc.go | 2 +- jsonschema/validate_test.go | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/jsonschema/doc.go b/jsonschema/doc.go index 8475743..eade338 100644 --- a/jsonschema/doc.go +++ b/jsonschema/doc.go @@ -95,7 +95,7 @@ are recorded in the schema, but ignored during validation. Minor and patch releases of this package may introduce behavior changes as part of bug fixes or correctness improvements. To help manage the impact of such -changes, the package allows you to lock in specific behaviors using the +changes, the package allows you to access previous behaviors using the `JSONSCHEMAGODEBUG` environment variable. The available settings are listed below; additional options may be introduced in future releases. diff --git a/jsonschema/validate_test.go b/jsonschema/validate_test.go index bcdd239..50bb9eb 100644 --- a/jsonschema/validate_test.go +++ b/jsonschema/validate_test.go @@ -509,7 +509,7 @@ func TestStructEmbedding(t *testing.T) { validInstance any }{ { - name: "ExportedPointer", + name: "Slice", targetType: reflect.TypeOf([]Banana{}), wantSchema: &Schema{ Types: []string{"null", "array"}, @@ -528,6 +528,28 @@ func TestStructEmbedding(t *testing.T) { {Apple: &Apple{ID: "foo2", Name: "Test Foo 2"}, Extra: "additional data 2"}, }, }, + { + name: "Array", + targetType: reflect.TypeOf([2]Banana{}), + wantSchema: &Schema{ + Type: "array", + MinItems: Ptr(2), + MaxItems: Ptr(2), + Items: &Schema{ + Type: "object", + Properties: map[string]*Schema{ + "id": {Type: "string"}, + "name": {Type: "string"}, + "extra": {Type: "string"}, + }, + Required: []string{"id", "name", "extra"}, + AdditionalProperties: falseSchema(), + }}, + validInstance: [2]Banana{ + {Apple: &Apple{ID: "foo1", Name: "Test Foo 2"}, Extra: "additional data 1"}, + {Apple: &Apple{ID: "foo2", Name: "Test Foo 2"}, Extra: "additional data 2"}, + }, + }, { name: "UnExportedPointer", targetType: reflect.TypeOf([]Durian{}),