Skip to content

Zero Values; Omitempty and Allownil

Brad Peabody edited this page Oct 27, 2023 · 2 revisions

OmitEmpty

Tagging a field on a struct with msg:"fieldname,omitempty" will cause the code generator to emit additional code to check if the field is empty (Go zero value) before writing it. The behavior of this option generally attempts to emulate that of the encoding/json package. Example:

type Example struct {
    A string `msg:"a,omitempty"`
    B int    `msg:"b,omitempty"`
    C string `msg:"c"`
}

In this example, A will be written if not "", B will be written if not 0 and C will always be written.

The omitempty option is ignored if used on a field where it is not supported.

The check for zero/empty is defined as false, 0, "", complex(0,0), nil, time.Time{} or YourStruct{}, according to type. Arrays, and structs with no type name are not supported. Note that, unlike encoding/json, named structs are supported as long as the struct is comparable.

It should be understood there are performance trade-offs when using the omitempty option. This is because the generated code must check fields to see if they are empty, write the map header with the correct length, and then do a separate pass to write each non-empty field. (Structs with no omitempty fields do not cause this additional code to be emitted.) Feel free run the benchmarks in _generated/omitempty_test.go for an example of the speed difference. A 10-15% speed decrease for the case of all fields being not-empty is a reasonable estimate. For the case of the majority of the fields being empty, it may perform significantly faster since field names and zero-length contents need not be written.

Decoding is not affected by this option. Fields omitted from the encode with this option are simply not touched during decode. Note that this means if you have existing code that re-uses the same struct instance, decoding into it multiple times, you'll need to zero it out (assign YourStruct{} to it) before decoding in order to get correct results using omitempty.

OmitZero

For custom (named) types with your own methods, the omitzero struct tag can be used instead of omitempty which will cause the code generator to check if a field should be omitted using a call to IsZero() bool on your type. This allows you to customize the exact behavior which should trigger omitting a field.

Example:

type ExampleStruct struct {
    A CustomType `msg:"a,omitzero"`
}

type CustomType struct {
    Valid bool
    Data string
}

// IsZero returns true if not Valid, also
// checks if the receiver is nil, so a nil
// pointer to this type will also return true.
func (ct *CustomType) IsZero() bool {
    return ct == nil || !ct.Valid
}

NOTE: This featured merged into master branch 27 Oct 2023, you may need to go install github.com/tinylib/msgp@master to use it. Can remove this note once it's in a tagged version.

Allownil

In Go maps and slices makes a distinction between 0 sized (empty) and nil (none) values. For other languages this distinction may not exist. By default msgp will add nil slices and maps as 0 sized equivalents.

As described above omitempty will omit these, meaning in structs the keys for these will be omitted. This cannot be done for tuples, since the output is of a fixed size, wherefore omitempty is ignored for tuples.

By adding the allownil option you allow for nil slices and maps to be added. Let's look at an example:

type Example struct {
    A []string `msg:"a,allownil"`
    B []int    `msg:"b,omitempty"`
    C []string `msg:"c"`
}

In this case A will always be added with key a. If the slice is nil, a nil value will be written. If it is non-nil, an array, including a 0 size will be added.

This contrasts B which will be completely omitted if the slice is nil, or added as an array for all other cases. While omitting may be enough information, it will not reset fields, for instance when reusing structs for decoding, and for tuples it will be ignored.

When using allownil the decoder must also be generated with this flag. However, it is safe to add allownil to decoders, and it will be compatible with content generated without allownil.

When encoding omitempty takes precedence over allownil, meaning the field will be omitted completely. It will only have an effect on slices and maps.