enum is a Go package that provides a code generator for type-safe, json/text-marshalable enumerations. Optional flags add SQL, BSON (MongoDB), and YAML support. It creates idiomatic Go code from simple type definitions, supporting both case-sensitive and case-insensitive string representations.
- Type-safe enum implementations
- Text marshaling/unmarshaling (JSON works via TextMarshaler)
- Optional SQL, BSON (MongoDB), and YAML support via flags
- Case-sensitive or case-insensitive string representations
- Alias support for parsing multiple string representations
- Panic-free parsing with error handling
- Must-style parsing variants for convenience
- Declaration order preservation (enums maintain source code order, not alphabetical)
- Type fidelity preservation (generated code uses the same underlying type as your enum)
- Optimized parsing with O(1) map-based lookups
- Smart SQL null handling (uses zero value when available, errors otherwise)
- Generated code is fully tested and documented
- No external runtime dependencies
- Supports Go 1.23's range-over-func iteration
Here's a minimal example showing how to define and use an enum:
//go:generate go run github.com/go-pkgz/enum@latest -type status -lower// Type must be lowercase (private)typestatusuint8// Constants must be prefixed with the type nameconst (
statusUnknownstatus=iotastatusActivestatusInactivestatusBlocked
)Run go generate to create the enum implementation. The generated code provides:
// use enum valuess:=StatusActivefmt.Println(s.String()) // prints: "active"// parse from strings, err:=ParseStatus("active")
iferr!=nil {
log.Fatal(err)
}
// use with JSONtypeDatastruct {
StatusStatus`json:"status"`
}
d:=Data{Status: StatusActive}
b, _:=json.Marshal(d)
fmt.Println(string(b)) // prints: {"status":"active"}// iterate over all values using Go 1.23 range-over-funcforstatus:=rangeStatusIter() {
fmt.Println(status) // prints each status in turn
}go install github.com/go-pkgz/enum@latest- Define your enum type and constants:
// Type name must be lowercase (private)typestatusuint8// Constants must be prefixed with the type nameconst (
statusUnknownstatus=iotastatusActivestatusInactivestatusBlocked
)- Add the generate directive:
//go:generate go run github.com/go-pkgz/enum@latest -type status- Run the generator:
go generate ./...By default the generated type supports encoding.TextMarshaler/Unmarshaler (used by encoding/json). To include other integrations, enable flags as needed (see below).
-type(required): the name of the type to generate enum for (must be lowercase/private)-path: output directory path (default: same as source)-lower: use lowercase for string representations when marshaling/unmarshaling-getter: enables the generation of an additional function,Get{{Type}}ByID, which attempts to find the corresponding enum element by its underlying integer ID. The-getterflag requires enum elements to have unique IDs to prevent undefined behavior.-sql(default: off): add SQL support viadatabase/sql/driver.Valuerandsql.Scanner-bson(default: off): add MongoDB BSON support viaMarshalBSONValue/UnmarshalBSONValue-yaml(default: off): add YAML support viagopkg.in/yaml.v3Marshaler/Unmarshaler-version: print version information-help: show usage information
The generator creates a new type with the following features:
- String representation (implements
fmt.Stringer) - Text marshaling (implements
encoding.TextMarshalerandencoding.TextUnmarshaler) - SQL support when
-sqlis set (implementsdatabase/sql/driver.Valuerandsql.Scanner) - Parse function with error handling (
ParseStatus) - uses efficient O(1) map lookup - Must-style parse function that panics on error (
MustStatus) - All possible values as package variable (
StatusValues) - preserves declaration order - All possible names as package variable (
StatusNames) - preserves declaration order - Index method to get underlying integer value (
Status.Index()) - Go 1.23 iterator support (
StatusIter()) for range-over-func syntax - Public constants for each value (
StatusActive,StatusInactive, etc.) - note that these are capitalized versions of your original constants
Additionally, if the -getter flag is set, a getter function (GetStatusByID) will be generated. This function allows retrieving an enum element using its raw integer ID.
- JSON: works out of the box through
encoding.TextMarshaler/Unmarshaler. - BSON (MongoDB): enable
-bsonto generateMarshalBSONValue/UnmarshalBSONValue; values are stored as strings. - YAML: enable
-yamlto generateMarshalYAML/UnmarshalYAML; values are encoded as strings.
Example (MongoDB using -bson):
//go:generate go run github.com/go-pkgz/enum@latest -type status -bson -lowertypestatusuint8const (
statusUnknownstatus=iotastatusActivestatusInactive
)
// Using mongo-go-drivertypeUserstruct {
ID primitive.ObjectID`bson:"_id,omitempty"`StatusStatus`bson:"status"`
}
// insert and readu:=User{Status: StatusActive}
_, _=coll.InsertOne(ctx, u) // stores { status: "active" }varoutUser_=coll.FindOne(ctx, bson.M{"status": "active"}).Decode(&out) // decodes via UnmarshalBSONValueThe -lower flag controls the output format of String() method:
// default (without -lower)StatusActive.String() // returns "Active"// with -lower flagStatusActive.String() // returns "active"Parsing is always case-insensitive regardless of the -lower flag:
// all of these work, with or without -lower flags1, _:=ParseStatus("active") // workss2, _:=ParseStatus("Active") // workss3, _:=ParseStatus("ACTIVE") // worksYou can define alternative string representations for enum values using inline comments with the enum:alias= directive. This is useful when you need to accept multiple input formats for the same value:
typepermissionintconst (
permissionNonepermission=iota// enum:alias=npermissionRead// enum:alias=rpermissionWrite// enum:alias=wpermissionReadWrite// enum:alias=rw,read-write
)With aliases defined, parsing accepts both canonical names and aliases:
// all of these parse to PermissionReadWritep1, _:=ParsePermission("ReadWrite") // canonical namep2, _:=ParsePermission("rw") // aliasp3, _:=ParsePermission("RW") // alias (case-insensitive)p4, _:=ParsePermission("read-write") // aliasAlias rules:
- Multiple aliases are separated by commas:
// enum:alias=rw,read-write - Parsing is always case-insensitive
- An alias cannot conflict with another constant's canonical name
- Duplicate aliases across different constants cause generation to fail
- The
String()method always returns the canonical name, not aliases
The -getter flag enables the generation of an additional function, Get{{Type}}ByID, which attempts to find the corresponding enum element by its underlying integer ID. If no matching element is found, an error is returned.
Note: The
-getterflag requires all IDs in the generated enum to be unique to prevent undefined behavior. If duplicate IDs are found, generation will fail with an error specifying which elements share the same ID.
The generated Parse function includes proper error handling:
status, err:=ParseStatus("invalid")
iferr!=nil {
// handle "invalid status: invalid" error
}
// or use Must variant if you're sure the value is validstatus:=MustStatus("active") // panics if invalidThe generated enums implement database/sql/driver.Valuer and sql.Scanner interfaces for seamless database integration:
// Scanning from databasevarsStatuserr:=db.QueryRow("SELECT status FROM users WHERE id = ?", userID).Scan(&s)
// Writing to database_, err=db.Exec("UPDATE users SET status = ? WHERE id = ?", StatusActive, userID)
// Handling NULL values// If the enum has a zero value (value = 0), NULL will scan to that value// Otherwise, scanning NULL returns an error- Parsing: O(1) constant time using map lookup (previously O(n) with switch statement)
- Values/Names access: Zero allocation - returns pre-computed package variables
Note:
StatusValuesandStatusNamesare exported slices. Do not modify them as this would affect all code using the enum.
- Memory efficient: Single shared instance for each enum value
- Declaration order: Preserved from source code, not alphabetically sorted
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License