Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion internal/pkg/formatter/yaml.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package formatter

import (
"encoding/json"
"fmt"
"regexp"
"strings"
Expand Down Expand Up @@ -56,7 +57,19 @@ func newYamlSerializer() *yamlSerializer {
}

func (s *yamlSerializer) serialize(v any, colored bool) (string, error) {
b, err := yaml.Marshal(v)
// there isn't really a good way to drop null values from YAML,
// so we pass it through the JSON marshaller first
jsonBytes, err := json.Marshal(v)
if err != nil {
return "", err
}

var cleaned any
if err := json.Unmarshal(jsonBytes, &cleaned); err != nil {
return "", err
}

b, err := yaml.Marshal(cleaned)
if err != nil {
return "", err
}
Expand Down
117 changes: 117 additions & 0 deletions internal/pkg/formatter/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ import (
"github.com/stretchr/testify/require"
)

// dummyStruct is a test struct with omitempty tags to verify null values are dropped
type dummyStruct struct {
Name string `json:"name"`
Age int `json:"age"`
Email *string `json:"email,omitempty"`
Phone *string `json:"phone,omitempty"`
Active bool `json:"active"`
Score *int `json:"score,omitempty"`
Nested *nestedStruct `json:"nested,omitempty"`
}

type nestedStruct struct {
Value string `json:"value"`
Optional *string `json:"optional,omitempty"`
}

func TestPrintYAML_TableDriven(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -97,3 +113,104 @@ negative: -10
})
}
}

func TestYamlSerializer_serialize_DropsNullValuesWithOmitempty(t *testing.T) {
tests := []struct {
name string
input any
colored bool
expected string
}{
{
name: "drops null pointer fields with omitempty",
input: dummyStruct{
Name: "Alice",
Age: 30,
Email: nil, // Should be dropped
Phone: nil, // Should be dropped
Active: true,
Score: nil, // Should be dropped
},
colored: false,
expected: `active: true
age: 30
name: Alice
`,
},
{
name: "keeps non-null pointer fields with omitempty",
input: dummyStruct{
Name: "Bob",
Age: 25,
Email: stringPtr("bob@example.com"),
Phone: nil, // Should be dropped
Active: false,
Score: intPtr(100),
},
colored: false,
expected: `active: false
age: 25
email: bob@example.com
name: Bob
score: 100
`,
},
{
name: "drops nested struct with omitempty when nil",
input: dummyStruct{
Name: "Charlie",
Age: 35,
Email: stringPtr("charlie@example.com"),
Active: false,
Nested: nil, // Should be dropped
},
colored: false,
expected: `active: false
age: 35
email: charlie@example.com
name: Charlie
`,
},
{
name: "keeps nested struct with omitempty when not nil",
input: dummyStruct{
Name: "David",
Age: 40,
Email: stringPtr("david@example.com"),
Active: false,
Nested: &nestedStruct{
Value: "test",
Optional: nil, // Should be dropped from nested struct
},
},
colored: false,
expected: `active: false
age: 40
email: david@example.com
name: David
nested:
value: test
`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
serializer := newYamlSerializer()
result, err := serializer.serialize(tt.input, tt.colored)
require.NoError(t, err)

assert.Equal(t, tt.expected, result)
// Verify that "null" doesn't appear in the output
assert.NotContains(t, result, "null")
})
}
}

func stringPtr(s string) *string {
return &s
}

func intPtr(i int) *int {
return &i
}