feat: add global OmitEmpty encoding option (#691)

Thanks for an amazing project! Go ecosystem struggle (e.g. https://github.com/GoogleCloudPlatform/prometheus-engine/issues/1629)
with 3P types that lack a good marshalling practices. Many project have
config structs only designed for parsing. See https://github.com/goccy/go-yaml/issues/306
for the detailed motivation.

Fixes: https://github.com/goccy/go-yaml/issues/306

The feature mechanism is simple -- we ignore struct tag setting for omitempty.
We assume it's always 'omitempty' if yaml.OmitEmpty() setting is set.

Signed-off-by: bwplotka <bwplotka@gmail.com>
This commit is contained in:
Bartlomiej Plotka 2025-04-11 12:13:15 +02:00 committed by GitHub
parent ba0598a7f0
commit ee37df774b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 34 additions and 9 deletions

View file

@ -38,6 +38,7 @@ type Encoder struct {
anchorNameMap map[string]struct{} anchorNameMap map[string]struct{}
anchorCallback func(*ast.AnchorNode, interface{}) error anchorCallback func(*ast.AnchorNode, interface{}) error
customMarshalerMap map[reflect.Type]func(interface{}) ([]byte, error) customMarshalerMap map[reflect.Type]func(interface{}) ([]byte, error)
omitEmpty bool
autoInt bool autoInt bool
useLiteralStyleIfMultiline bool useLiteralStyleIfMultiline bool
commentMap map[*Path][]*Comment commentMap map[*Path][]*Comment
@ -824,7 +825,7 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column
} }
fieldValue := value.FieldByName(field.Name) fieldValue := value.FieldByName(field.Name)
structField := structFieldMap[field.Name] structField := structFieldMap[field.Name]
if structField.IsOmitEmpty && e.isZeroValue(fieldValue) { if (e.omitEmpty || structField.IsOmitEmpty) && e.isZeroValue(fieldValue) {
// omit encoding // omit encoding
continue continue
} }

View file

@ -465,7 +465,7 @@ func TestEncoder(t *testing.T) {
nil, nil,
}, },
// Conditional flag // Omitempty flag.
{ {
"a: 1\n", "a: 1\n",
struct { struct {
@ -482,7 +482,6 @@ func TestEncoder(t *testing.T) {
}{0, 0}, }{0, 0},
nil, nil,
}, },
{ {
"a:\n \"y\": \"\"\n", "a:\n \"y\": \"\"\n",
struct { struct {
@ -496,7 +495,6 @@ func TestEncoder(t *testing.T) {
}{}}, }{}},
nil, nil,
}, },
{ {
"a: {}\n", "a: {}\n",
struct { struct {
@ -510,7 +508,6 @@ func TestEncoder(t *testing.T) {
}{}}, }{}},
nil, nil,
}, },
{ {
"a: {x: 1}\n", "a: {x: 1}\n",
struct { struct {
@ -518,7 +515,6 @@ func TestEncoder(t *testing.T) {
}{&struct{ X, y int }{1, 2}}, }{&struct{ X, y int }{1, 2}},
nil, nil,
}, },
{ {
"{}\n", "{}\n",
struct { struct {
@ -526,7 +522,6 @@ func TestEncoder(t *testing.T) {
}{nil}, }{nil},
nil, nil,
}, },
{ {
"a: {x: 0}\n", "a: {x: 0}\n",
struct { struct {
@ -534,7 +529,6 @@ func TestEncoder(t *testing.T) {
}{&struct{ X, y int }{}}, }{&struct{ X, y int }{}},
nil, nil,
}, },
{ {
"a: {x: 1}\n", "a: {x: 1}\n",
struct { struct {
@ -567,8 +561,29 @@ func TestEncoder(t *testing.T) {
}, },
nil, nil,
}, },
// OmitEmpty global option.
{
"a: 1\n",
struct {
A int
B int `yaml:"b,omitempty"`
}{1, 0},
[]yaml.EncodeOption{
yaml.OmitEmpty(),
},
},
{
"{}\n",
struct {
A int
B int `yaml:"b,omitempty"`
}{0, 0},
[]yaml.EncodeOption{
yaml.OmitEmpty(),
},
},
// Flow flag // Flow flag.
{ {
"a: [1, 2]\n", "a: [1, 2]\n",
struct { struct {

View file

@ -215,6 +215,15 @@ func AutoInt() EncodeOption {
} }
} }
// OmitEmpty forces the encoder to assume an `omitempty` struct tag is
// set on all the fields. See `Marshal` commentary for the `omitempty` tag logic.
func OmitEmpty() EncodeOption {
return func(e *Encoder) error {
e.omitEmpty = true
return nil
}
}
// CommentPosition type of the position for comment. // CommentPosition type of the position for comment.
type CommentPosition int type CommentPosition int