Skip to content

nikolaydubina/go-enum-encoding

Repository files navigation

go-enum-encoding

Go Report Card Go Reference codecov go-recipes OpenSSF Scorecard

go install github.com/nikolaydubina/go-enum-encoding@latest
  • 200 LOC
  • simple, fast1, strict1
  • generates encoding/decoding, tests and benchmarks
type Color struct{ c uint8 }

//go:generate go-enum-encoding -type=Color
var (
	UndefinedColor = Color{} 	// json:""
	Red            = Color{1}	// json:"red"
	Green          = Color{2}	// json:"green"
	Blue           = Color{3}	// json:"blue"
)

It also works with raw iota enums:

type Size uint8

//go:generate go-enum-encoding -type=Size
const (
	UndefinedSize Size = iota // json:""
	Small                     // json:"small"
	Large                     // json:"large"
	XLarge                    // json:"xlarge"
)

Related Work and References

  • http://github.com/zarldev/goenums - does much more advanced struct generation, generates all enum utilities besides encoding, does not generate tests, uses similar notation to trigger go:generate but with different comment directives (non-json field tags)
Appendix: Performance

@mishak87 proposed to use array instead of map for performance. Similarly, @nikolaydubina faced degradation in performance for loop based array for large enum sets (256 values) while working on fpmoney2 and iso42173.

$ go test -bench=Benchmark -benchmem ./internal/research/map >  map.bench
$ go test -bench=Benchmark -benchmem ./internal/research/inline > inline.bench
$ go test -bench=Benchmark -benchmem ./internal/research/array-loop > array-loop.bench 
$ go test -bench=Benchmark -benchmem ./internal/research/array-index > array-index.bench
$ go test -bench=Benchmark -benchmem ./internal/research/uint-array > uint-array.bench
$ go test -bench=Benchmark -benchmem ./internal/research/uint-inline > uint-inline.bench
$ benchstat -split="XYZ" map.bench inline.bench array-loop.bench array-index.bench uint-array.bench uint-inline.bench
name \ time/op          map.bench    inline.bench  array-loop.bench  array-index.bench  uint-array.bench  uint-inline.bench
MarshalText_Color-16    22.3ns ± 0%    5.3ns ± 0%        7.5ns ± 0%         2.2ns ± 0%        1.9ns ± 0%         5.0ns ± 0%
UnmarshalText_Color-16  11.9ns ± 0%    5.7ns ± 0%       14.5ns ± 0%        11.8ns ± 0%       14.3ns ± 0%         5.7ns ± 0%

name \ alloc/op         map.bench    inline.bench  array-loop.bench  array-index.bench  uint-array.bench  uint-inline.bench
MarshalText_Color-16     0.00B         0.00B             0.00B              0.00B             0.00B              0.00B     
UnmarshalText_Color-16   0.00B         0.00B             0.00B              0.00B             0.00B              0.00B     

name \ allocs/op        map.bench    inline.bench  array-loop.bench  array-index.bench  uint-array.bench  uint-inline.bench
MarshalText_Color-16      0.00          0.00              0.00               0.00              0.00               0.00     
UnmarshalText_Color-16    0.00          0.00              0.00               0.00              0.00               0.00 
Appendix: Stringer

String() string method is very similar to encoding, howver it does not return error and returns string instead of []byte. To avoid malloc and convenience, stringer option is added to generate it accompanied with tests and benchmarks. First used in tailscale.

Appendix: Custom Methods

Some enums are encoded directly as underlying basic type, however they have dual custom use through String() string methods and alike. To assist safe migration, custom encode and decode method names are added. First used in tailscale.

Footnotes

  1. Comparison to other enums methods: http://github.com/nikolaydubina/go-enum-example 2

  2. iso4217 enums performance loop vs map: https://github.com/ferdypruis/iso4217/issues/4

  3. fpmoney: https://github.com/nikolaydubina/fpmoney?tab=readme-ov-file#appendix-a-jsonunmarshal-optimizations