Skip to content

Add support for YAML (un)marshalling #11

Open

Description

@jamietanna

This would allow us to simplify configuration in oapi-codegen itself where we're using *string or *bools, and should be straightforward enough

Activity

  1. jamietanna commented on Feb 17, 2026

    @jamietanna
    MemberAuthor

    Via Claude Sonnet 4.5:

    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
    
    ```go
    type MyStruct struct {
        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 recursion
        type rawStruct MyStruct
        raw := rawStruct{}
        
        // Manually handle null values for Nullable fields
        if node.Kind == yaml.MappingNode {
            for i := 0; i < len(node.Content); i += 2 {
                key := node.Content[i].Value
                value := node.Content[i+1]
                
                switch key {
                case "foo":
                    if value.Tag == "!!null" {
                        raw.Foo.SetNull()
                    } else {
                        var v string
                        if err := value.Decode(&v); err != nil {
                            return err
                        }
                        raw.Foo.Set(v)
                    }
                case "bar":
                    if value.Tag == "!!null" {
                        raw.Bar.SetNull()
                    } else {
                        var v int
                        if err := value.Decode(&v); err != nil {
                            return err
                        }
                        raw.Bar.Set(v)
                    }
                }
            }
        }
        
        *s = MyStruct(raw)
        return nil
    }

    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
    • Null fields output as foo: null
    • Fields with values output normally: foo: bar
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions