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)
type status uint8
// Constants must be prefixed with the type name
const (
statusUnknown status = iota
statusActive
statusInactive
statusBlocked
)Run go generate to create the enum implementation. The generated code provides:
// use enum values
s := StatusActive
fmt.Println(s.String()) // prints: "active"
// parse from string
s, err := ParseStatus("active")
if err != nil {
log.Fatal(err)
}
// use with JSON
type Data struct {
Status Status `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-func
for status := range StatusIter() {
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)
type status uint8
// Constants must be prefixed with the type name
const (
statusUnknown status = iota
statusActive
statusInactive
statusBlocked
)- 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 -lower
type status uint8
const (
statusUnknown status = iota
statusActive
statusInactive
)
// Using mongo-go-driver
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Status Status `bson:"status"`
}
// insert and read
u := User{Status: StatusActive}
_, _ = coll.InsertOne(ctx, u) // stores { status: "active" }
var out User
_ = 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 flag
StatusActive.String() // returns "active"Parsing is always case-insensitive regardless of the -lower flag:
// all of these work, with or without -lower flag
s1, _ := ParseStatus("active") // works
s2, _ := ParseStatus("Active") // works
s3, _ := 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:
type permission int
const (
permissionNone permission = iota // enum:alias=n
permissionRead // enum:alias=r
permissionWrite // enum:alias=w
permissionReadWrite // enum:alias=rw,read-write
)With aliases defined, parsing accepts both canonical names and aliases:
// all of these parse to PermissionReadWrite
p1, _ := ParsePermission("ReadWrite") // canonical name
p2, _ := ParsePermission("rw") // alias
p3, _ := 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")
if err != nil {
// handle "invalid status: invalid" error
}
// or use Must variant if you're sure the value is valid
status := MustStatus("active") // panics if invalidThe generated enums implement database/sql/driver.Valuer and sql.Scanner interfaces for seamless database integration:
// Scanning from database
var s Status
err := 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