You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixed YAML null handling for the Nullable type. The issue was caused by a known limitation in gopkg.in/yaml.v3 where
UnmarshalYAML is not called for null values on map types.
### Changes Made:1. nullable.go:122-138 - Updated UnmarshalYAML to properly detect YAML null values
2. nullable.go:145-149 - Fixed MarshalYAML to return nil for null values instead of erroring
3. internal/test/nullable_test.go:17-45 - Added custom UnmarshalYAML on the test struct to work around yaml.v3
limitation
4. internal/test/nullable_test.go:171,184 - Fixed test assertions to use serializeYAML instead of serialize
5. nullable.go:11-28 - Updated documentation to explain YAML limitation
6. YAML_SUPPORT.md - Created comprehensive documentation with examples
All tests now pass ✓
and:
# YAML Support for Nullable Types## Overview
The `Nullable` type now supports YAML marshalling and unmarshalling, with one important limitation due to the `gopkg.in/yaml.v3` library.
## The Issue
The yaml.v3 library has a known limitation where `UnmarshalYAML` is **not called for null values** on map types. Since `Nullable[T]` is implemented as `map[bool]T`, this means that when YAML contains an explicit `null` value (e.g., `foo: null`), the yaml library will simply set the field to `nil` instead of calling our custom `UnmarshalYAML` method.
This is tracked in several yaml.v3 issues:
-https://github.com/go-yaml/yaml/issues/276-https://github.com/go-yaml/yaml/issues/681-https://github.com/go-yaml/yaml/pull/689 (attempted fix, never merged)
The yaml.v3 repository was archived on April 1, 2025, so this limitation will not be fixed.
## The Solution
To properly handle YAML null values, the **parent struct** must implement a custom `UnmarshalYAML` method that manually checks for null values and calls `SetNull()` on the `Nullable` field.
### Example Implementation```gotypeMyStructstruct {
Foo nullable.Nullable[string] `yaml:"foo,omitempty"`Bar nullable.Nullable[int] `yaml:"bar,omitempty"`
}
func(s *MyStruct) UnmarshalYAML(node *yaml.Node) error {
// Use a type alias to avoid recursiontype rawStruct MyStruct
raw:= rawStruct{}
// Manually handle null values for Nullable fieldsif node.Kind == yaml.MappingNode {
fori:=0; i < len(node.Content); i += 2 {
key:= node.Content[i].Valuevalue:= node.Content[i+1]
switch key {
case"foo":
if value.Tag == "!!null" {
raw.Foo.SetNull()
} else {
varvstringiferr:= value.Decode(&v); err != nil {
return err
}
raw.Foo.Set(v)
}
case"bar":
if value.Tag == "!!null" {
raw.Bar.SetNull()
} else {
varvintiferr:= value.Decode(&v); err != nil {
return err
}
raw.Bar.Set(v)
}
}
}
}
*s = MyStruct(raw)
returnnil
}
Behavior Summary
YAML Input
Result
IsSpecified()
IsNull()
foo: bar
Nullable with value "bar"
true
false
foo: null
Nullable with explicit null
true
true
(field omitted)
Nullable as nil (unspecified)
false
false
MarshalYAML
The MarshalYAML method works correctly without requiring custom parent implementations:
Unspecified fields (when using omitempty) are omitted from output
Activity
nullable.Nullables for configuration oapi-codegen#1729jamietanna commentedon Feb 17, 2026
Via Claude Sonnet 4.5:
and:
Behavior Summary
foo: barfoo: nullMarshalYAML
The
MarshalYAMLmethod works correctly without requiring custom parent implementations:omitempty) are omitted from outputfoo: nullfoo: bar