diff --git a/docs/docs/features/request-validation.md b/docs/docs/features/request-validation.md index 1626dcce..74a10745 100644 --- a/docs/docs/features/request-validation.md +++ b/docs/docs/features/request-validation.md @@ -57,13 +57,14 @@ Huma tries to balance schema simplicity, usability, and broad compatibility with Fields being nullable is determined automatically but can be overridden as needed using the logic below: 1. Start with no fields as nullable -2. If a field is a pointer: +2. If a field is a pointer (including slices): 1. To a `boolean`, `integer`, `number`, `string`: it is nullable unless it has `omitempty`. - 2. To an `array`, `object`: it is **not** nullable, due to complexity and bad support for `anyOf`/`oneOf` in many tools. + 2. To an `array`: it is nullable if `huma.DefaultArrayNullable` is true. + 3. To an `object`: it is **not** nullable, due to complexity and bad support for `anyOf`/`oneOf` in many tools. 3. If a field has `nullable:"false"`, it is not nullable 4. If a field has `nullable:"true"`: - 1. To a `boolean`, `integer`, `number`, `string`: it is nullable - 2. To an `array`, `object`: **panic** saying this is not currently supported + 1. To a `boolean`, `integer`, `number`, `string`, `array`: it is nullable + 2. To an `object`: **panic** saying this is not currently supported 5. If a struct has a field `_` with `nullable: true`, the struct is nullable enabling users to opt-in for `object` without the `anyOf`/`oneOf` complication. Here are some examples: @@ -77,7 +78,7 @@ type MyStruct1 struct { } // Make a specific scalar field nullable. This is *not* supported for -// slices, maps, or structs. Structs *must* use the method above. +// maps or structs. Structs *must* use the method above. type MyStruct2 struct { Field1 *string `json:"field1"` Field2 string `json:"field2" nullable:"true"` @@ -86,6 +87,10 @@ type MyStruct2 struct { Nullable types will generate a type array like `"type": ["string", "null"]` which has broad compatibility and is easy to downgrade to OpenAPI 3.0. Also keep in mind you can always provide a [custom schema](./schema-customization.md) if the built-in features aren't exactly what you need. +!!! info "Note" + + Slices in Go marshal into JSON as `null` if the slice itself is `nil` rather than allocated but empty. This is why slices are nullable by default. See the [Go JSON package documentation](https://pkg.go.dev/encoding/json#Marshal) for more information. + ## Validation Tags The following additional tags are supported on model fields: diff --git a/schema_test.go b/schema_test.go index f1b3b092..e3e671fd 100644 --- a/schema_test.go +++ b/schema_test.go @@ -722,6 +722,46 @@ func TestSchema(t *testing.T) { "required": ["int"] }`, }, + { + name: "field-nullable-array", + input: struct { + Int []int64 `json:"int" nullable:"true"` + }{}, + expected: `{ + "type": "object", + "additionalProperties": false, + "properties": { + "int": { + "type": ["array", "null"], + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "required": ["int"] + }`, + }, + { + name: "field-non-nullable-array", + input: struct { + Int []int64 `json:"int" nullable:"false"` + }{}, + expected: `{ + "type": "object", + "additionalProperties": false, + "properties": { + "int": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + "required": ["int"] + }`, + }, { name: "field-nullable-struct", input: struct {