enum

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 6 Imported by: 0

README

enum

零依赖的类型安全枚举库,支持命名值集合、Struct 泛型反射构造,以及 Tree 递归多级下拉渲染。

快速开始

// 方式一:纯 Enum — 手写 Item 列表
var WeekdayEnum = enum.New(
    enum.ItemFrom("Monday", "周一", time.Monday),
    enum.ItemFrom("Tuesday", "周二", time.Tuesday),
    // ...
)

// 方式二:Struct — struct tag 反射构造
type Priorities struct {
    enum.Struct[Priority]
    Low    Priority `enum:"0,低"`
    Medium Priority `enum:"1,中"`
    High   Priority `enum:"2,高,disabled"`
}
p := enum.InitFor[Priority, Priorities]()

类型

Item[T] — 枚举成员
字段 类型 用途
key string 程序化标识,如 "UserCreated"
name string 展示名,如 "用户创建"
value T 枚举值 int/uint/string
disabled bool 是否禁用(仅展示,不可选)
ext map[string]string 扩展元数据

访问器: Key() Name() Value() IsDisabled() Ext()

构造: ItemFrom(key, name string, value T, opts ...ItemOption[T]) Item[T]

选项: WithDisabled[T]() WithExt[T](map[string]string)

Enum[T] — 枚举集合

有序、逻辑不可变(只有 ext 可写)。并发读安全(AddExt 与读调用不并发时)。

方法 说明
ByKey(key) (Item[T], bool) 按 key 查找
MustByKey(key) Item[T] 按 key 查找,找不到 panic
ByValue(value) (Item[T], bool) 按 value 查找
MustByValue(value) Item[T] 按 value 查找,找不到 panic
Index(i) (Item[T], bool) 按位置索引
MustIndex(i) Item[T] 索引,越界 panic
Contains(value) bool 值是否存在
All() []Item[T] 所有成员(定义顺序)
Keys() []string 所有 key
Values() []T 所有 value
Len() int 成员数量
Range() iter.Seq2[string, Item[T]] Go 1.23 迭代器
AddExt(itemKey, extKey, extValue string) 添加扩展元数据
GetExt(itemKey) map[string]string 获取扩展元数据
ToMap() map[string]map[string]any 转为 map(前端 O(1) 查找)

JSON 序列化:

[
  {"key":"Low","name":"低","value":0},
  {"key":"Medium","name":"中","value":1},
  {"key":"High","name":"高","disabled":true,"value":2}
]
Struct[T] — struct tag 驱动构造

通过反射读 enum:"value,name[,disabled]" tag 自动构建:

type EventTopics struct {
    enum.Struct[string]
    UserCreated string `enum:"user.created,用户创建"`
    OrderPaid   string `enum:"order.paid,订单已付"`
}
events := enum.InitFor[string, EventTopics]()

// 字段即常量
switch topic { case events.UserCreated: ... }

// 通过 .Enum() 拿到 *Enum[string],所有 Enum 方法都可用
events.ByKey("UserCreated") // Item[string], true
events.All()                // []Item[string]
events.Enum().MarshalJSON() // JSON
TreeNode / BuildTree / AsStringItems — 多级下拉

ext["parent"] 将平面列表转为递归树:

type Area int
type Areas struct {
    enum.Struct[Area]
    China     Area `enum:"101,中国"`
    Guangdong Area `enum:"102,广东"`
    Shenzhen  Area `enum:"103,深圳"`
}
areas := enum.InitFor[Area, Areas]()
areas.AddExt("Guangdong", enum.ParentExtKey, "101")
areas.AddExt("Shenzhen",  enum.ParentExtKey, "102")

tree := enum.BuildTree(areas.All())
// JSON:
// [{key:"China",name:"中国",value:"101",children:[
//   {key:"Guangdong",name:"广东",value:"102",children:[
//     {key:"Shenzhen",name:"深圳",value:"103"}
//   ]}
// ]}]

json.NewEncoder(w).Encode(tree)

异构枚举合并:

all := append(enum.AsStringItems(cats.All()), enum.AsStringItems(prods.All())...)
tree := enum.BuildTree(all)

常量

常量 用途
ParentExtKey "parent" BuildTree 的 ext key
NameKey "name" ToMap 的展示名字段
ValueKey "value" ToMap 的值字段
DisabledKey "disabled" 禁用字段
ExtKey "ext" 扩展元数据字段

枚举值类型约束

EnumBase 支持:

~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~string

自定义类型只要底层是 int/uint/string 即可(type Status int 等)。

多级下拉示例

水果 ──┬── 苹果
       └── 香蕉
蔬菜 ──── 胡萝卜
prods.AddExt("Apple",  enum.ParentExtKey, "FRUIT")
prods.AddExt("Banana", enum.ParentExtKey, "FRUIT")
prods.AddExt("Carrot", enum.ParentExtKey, "VEGETABLE")

添加新领域枚举

// 1. 定义类型
type MyEnum int
type MyEnums struct {
    enum.Struct[MyEnum]
    Alpha MyEnum `enum:"0,阿尔法"`
    Beta  MyEnum `enum:"1,贝塔"`
}

// 2. 初始化
var E = enum.InitFor[MyEnum, MyEnums]()

// 3. 使用 E

设计原则

  • 零外部依赖 — 仅使用 Go 标准库
  • enum tag 格式:"value,name[,disabled]" — 第三段 "disabled" 标记为仅展示不可选
  • 没有 init() — 枚举显式构造,无隐式副作用

Documentation

Index

Examples

Constants

View Source
const (
	NameKey     = "name"
	ValueKey    = "value"
	DisabledKey = "disabled"
	ExtKey      = "ext"
)

JSON field names shared between jsonItem struct tags and ToMap output.

View Source
const ParentExtKey = "parent"

ParentExtKey is the ext key used by BuildTree to establish parent-child relationships. Items with ext[ParentExtKey] == "…" are treated as children of the item with the matching value.

Variables

This section is empty.

Functions

func InitFor

func InitFor[T EnumBase, R any]() R

InitFor is a one-liner: allocates, inits, and returns a value of R. It delegates to Init so struct validation stays in one place.

p := enum.InitFor[Priority, Priorities]()

Types

type Enum

type Enum[T EnumBase] struct {
	// contains filtered or unexported fields
}

Enum is an ordered set of named values. It is logically immutable — items, names, and values are fixed after construction. The only exception is AddExt, which mutates the extension metadata of an existing item; all other fields remain stable.

It is safe for concurrent use after construction, provided AddExt calls are not concurrent with reads.

Example (RoundTrip)
// Create, marshal, unmarshal back — end-to-end.
e := enum.InitFor[OrderStatus, OrderStatuses]()
e.AddExt("Processing", "color", "blue")

// Marshal.
b, _ := json.Marshal(e.Enum())

// Unmarshal into a fresh Enum.
var restored enum.Enum[OrderStatus]
json.Unmarshal(b, &restored)

// Verify.
item, _ := restored.ByKey("Processing")
fmt.Println(item.Name(), item.Ext()["color"])
Output:
处理中 blue

func New

func New[T EnumBase](items ...Item[T]) *Enum[T]

New creates an Enum from the given items. Panics if no items are provided, or if any name or value is duplicated.

func (*Enum[T]) AddExt

func (e *Enum[T]) AddExt(itemKey, extKey, extValue string)

AddExt adds a key-value pair to the named item's extension metadata. No-op when the item is not found. The item's ext map is created on first use. Updates both the items slice and the lookup maps so subsequent ByKey/ByValue calls reflect the change.

func (*Enum[T]) All

func (e *Enum[T]) All() []Item[T]

All returns a copy of all items in definition order.

func (*Enum[T]) ByKey

func (e *Enum[T]) ByKey(key string) (Item[T], bool)

ByKey returns the item with the given key. The second return value is false when the key does not exist.

func (*Enum[T]) ByValue

func (e *Enum[T]) ByValue(value T) (Item[T], bool)

ByValue returns the item with the given value. The second return value is false when the value does not exist.

func (*Enum[T]) Contains

func (e *Enum[T]) Contains(value T) bool

Contains reports whether the given value is a member of the enum.

func (*Enum[T]) GetExt

func (e *Enum[T]) GetExt(itemKey string) map[string]string

GetExt returns a copy of the named item's extension metadata, or nil when the item is not found.

func (*Enum[T]) Index

func (e *Enum[T]) Index(i int) (Item[T], bool)

Index returns the item at the given position in definition order. The second return value is false when i is out of range.

func (*Enum[T]) Keys

func (e *Enum[T]) Keys() []string

Keys returns all programmatic keys in definition order.

func (*Enum[T]) Len

func (e *Enum[T]) Len() int

Len returns the number of items.

func (*Enum[T]) MarshalJSON

func (e *Enum[T]) MarshalJSON() ([]byte, error)

MarshalJSON serializes the enum as an ordered array of {key, name, value, disabled?, ext?}.

Example (Int)
statuses := enum.InitFor[OrderStatus, OrderStatuses]()
b, _ := json.Marshal(statuses.Enum())
fmt.Println(string(b))
Output:
[{"key":"Pending","name":"待处理","value":0},{"key":"Processing","name":"处理中","value":1},{"key":"Shipped","name":"已发货","value":2},{"key":"Cancelled","name":"已取消","value":3,"disabled":true}]
Example (String)
e := enum.InitFor[Severity, SeverityEnum]()
b, _ := json.Marshal(e.Enum())
fmt.Println(string(b))
Output:
[{"key":"Info","name":"信息","value":"INFO"},{"key":"Warn","name":"警告","value":"WARN"},{"key":"Error","name":"错误","value":"ERROR"}]

func (*Enum[T]) MustByKey

func (e *Enum[T]) MustByKey(key string) Item[T]

MustByKey returns the item with the given key, panicking if it doesn't exist.

func (*Enum[T]) MustByValue

func (e *Enum[T]) MustByValue(value T) Item[T]

MustByValue returns the item with the given value, panicking if it doesn't exist.

func (*Enum[T]) MustIndex

func (e *Enum[T]) MustIndex(i int) Item[T]

MustIndex returns the item at position i, panicking if out of range.

func (*Enum[T]) Range

func (e *Enum[T]) Range() iter.Seq2[string, Item[T]]

Range iterates over all items in definition order, yielding (key, item).

func (*Enum[T]) ToMap

func (e *Enum[T]) ToMap() map[string]map[string]any

ToMap returns each item keyed by its key. Useful when the frontend needs O(1) lookup by key instead of array iteration:

json.NewEncoder(w).Encode(event.Events.ToMap())
// → {UserCreated: {name:"用户创建", value:"user.created"}, ...}
Example

ToMap as JSON.

e := enum.InitFor[Severity, SeverityEnum]()
b, _ := json.Marshal(e.ToMap())
fmt.Println(string(b))
Output:
{"Error":{"name":"错误","value":"ERROR"},"Info":{"name":"信息","value":"INFO"},"Warn":{"name":"警告","value":"WARN"}}

func (*Enum[T]) UnmarshalJSON

func (e *Enum[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON reconstructs the enum from a JSON array. Returns an error on duplicate names/values or invalid JSON.

Example
data := []byte(`[{"key":"Low","name":"低","value":0},{"key":"High","name":"高","value":2}]`)
var e enum.Enum[Priority]
if err := json.Unmarshal(data, &e); err != nil {
	panic(err)
}
item, _ := e.ByKey("High")
fmt.Println("found:", item.Name(), "→", item.Value())
Output:
found: 高 → 2

func (*Enum[T]) Values

func (e *Enum[T]) Values() []T

Values returns all values in definition order.

type EnumBase

type EnumBase interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~string
}

EnumBase is the type constraint for enum value types. Only int/uint/string-based types are supported.

type Item

type Item[T EnumBase] struct {
	// contains filtered or unexported fields
}

Item is one member of an enum—a programmatic key, a human-readable name, and a value. Fields are private; use the accessor methods to read them.

func AsStringItems

func AsStringItems[T EnumBase](items []Item[T]) []Item[string]

AsStringItems converts a typed Item slice to []Item[string] using fmt.Sprint for the value. This allows combining items from different enum types into a single BuildTree input.

func ItemFrom

func ItemFrom[T EnumBase](key, name string, value T, opts ...ItemOption[T]) Item[T]

ItemFrom creates an Item. key is the programmatic identifier (e.g. "Monday"); name is the human-readable label (e.g. "周一"). Options like WithDisabled and WithExt are applied last.

func (Item[T]) Ext

func (i Item[T]) Ext() map[string]string

func (Item[T]) IsDisabled

func (i Item[T]) IsDisabled() bool

func (Item[T]) Key

func (i Item[T]) Key() string

func (Item[T]) Name

func (i Item[T]) Name() string

func (Item[T]) Value

func (i Item[T]) Value() T

type ItemOption

type ItemOption[T EnumBase] func(*Item[T])

ItemOption is a functional option for ItemFrom.

func WithDisabled

func WithDisabled[T EnumBase]() ItemOption[T]

WithDisabled marks the item as disabled (display-only, not selectable).

func WithExt

func WithExt[T EnumBase](m map[string]string) ItemOption[T]

WithExt sets extension metadata on the item.

type Struct

type Struct[T EnumBase] struct {
	// contains filtered or unexported fields
}

Struct is a reflection-driven enum backed by Enum[T]. Use InitFor:

type Statuses struct {
    Struct[Status]  // embed anywhere (found by scanning anonymous fields)
    Pending Status  `enum:"0,待处理"`
    Active  Status  `enum:"1,活跃"`
}

s := enum.InitFor[Status, Statuses]()
s.ByKey("Active")     // → Item, true
switch v { case s.Pending: ... }
Example (Embedded_marshal)
e := enum.InitFor[Severity, SeverityEnum]()
b, _ := json.Marshal(e.Enum())
var items []map[string]any
json.Unmarshal(b, &items)
fmt.Println(items[0]["key"], items[0]["name"])
Output:
Info 信息
Example (Embedded_unmarshal)
// Unmarshal replaces the underlying enum data.
data := []byte(`[{"key":"A","name":"甲","value":1}]`)
var e enum.Enum[Priority]
json.Unmarshal(data, &e)
fmt.Println(e.Len(), e.Contains(Priority(1)))
Output:
1 true

func (*Struct[T]) AddExt

func (s *Struct[T]) AddExt(itemKey, extKey, extValue string)

func (*Struct[T]) All

func (s *Struct[T]) All() []Item[T]

func (*Struct[T]) ByKey

func (s *Struct[T]) ByKey(name string) (Item[T], bool)

func (*Struct[T]) ByValue

func (s *Struct[T]) ByValue(value T) (Item[T], bool)

func (*Struct[T]) Contains

func (s *Struct[T]) Contains(value T) bool

func (*Struct[T]) Enum

func (s *Struct[T]) Enum() *Enum[T]

Enum returns the underlying Enum[T]. When Struct is embedded in another struct that has its own exported fields, Go's JSON encoder ignores the embedded MarshalJSON/UnmarshalJSON — use .Enum() to serialize/deserialize directly:

json.NewEncoder(w).Encode(event.Events.Enum())       // marshal
json.NewDecoder(r).Decode(event.Events.Enum())       // unmarshal

func (*Struct[T]) GetExt

func (s *Struct[T]) GetExt(itemKey string) map[string]string

func (*Struct[T]) Index

func (s *Struct[T]) Index(i int) (Item[T], bool)

func (*Struct[T]) Keys

func (s *Struct[T]) Keys() []string

func (*Struct[T]) Len

func (s *Struct[T]) Len() int

func (*Struct[T]) MarshalJSON

func (s *Struct[T]) MarshalJSON() ([]byte, error)

MarshalJSON / UnmarshalJSON delegate to the underlying enum. These are only effective when marshaling a bare Struct[T] (not embedded in a struct with exported fields). For the embedded case, use .Enum().

Example
p := enum.InitFor[Priority, Priorities]()
b, _ := json.Marshal(p)
fmt.Println(string(b))
Output:
{"Low":0,"Medium":1,"High":2}
Example (Struct)
p := enum.InitFor[Priority, Priorities]()
sb, _ := json.Marshal(p.Struct)
fmt.Println(string(sb))
Output:
{}

func (*Struct[T]) MustByKey

func (s *Struct[T]) MustByKey(name string) Item[T]

func (*Struct[T]) MustByValue

func (s *Struct[T]) MustByValue(value T) Item[T]

func (*Struct[T]) MustIndex

func (s *Struct[T]) MustIndex(i int) Item[T]

func (*Struct[T]) Range

func (s *Struct[T]) Range() iter.Seq2[string, Item[T]]

func (*Struct[T]) ToMap

func (s *Struct[T]) ToMap() map[string]map[string]any

func (*Struct[T]) UnmarshalJSON

func (s *Struct[T]) UnmarshalJSON(data []byte) error

func (*Struct[T]) Values

func (s *Struct[T]) Values() []T

type TreeNode

type TreeNode struct {
	Key      string     `json:"key"`
	Name     string     `json:"name"`
	Value    string     `json:"value"`
	Disabled bool       `json:"disabled,omitempty"`
	Children []TreeNode `json:"children,omitempty"`
}

TreeNode is a recursive frontend node for multi-level dropdowns.

func BuildTree

func BuildTree[T EnumBase](items []Item[T]) []TreeNode

BuildTree converts a flat item list into a recursive tree keyed by ext["parent"]. Items without a parent (or whose parent doesn't match any item) become roots.

Example

BuildTree produces nested JSON for multi-level dropdowns.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/donnol/enum"
)

func main() {
	items := []enum.Item[string]{
		enum.ItemFrom("Fruit", "水果", "FRUIT"),
		enum.ItemFrom("Vegetable", "蔬菜", "VEGETABLE"),
		enum.ItemFrom("Apple", "苹果", "APPLE",
			enum.WithExt[string](map[string]string{enum.ParentExtKey: "FRUIT"})),
		enum.ItemFrom("Banana", "香蕉", "BANANA",
			enum.WithExt[string](map[string]string{enum.ParentExtKey: "FRUIT"}),
			enum.WithDisabled[string]()),
		enum.ItemFrom("Carrot", "胡萝卜", "CARROT",
			enum.WithExt[string](map[string]string{enum.ParentExtKey: "VEGETABLE"})),
	}

	tree := enum.BuildTree(items)
	b, _ := json.MarshalIndent(tree, "", "  ")
	fmt.Println(string(b))

}
Output:
[
  {
    "key": "Fruit",
    "name": "水果",
    "value": "FRUIT",
    "children": [
      {
        "key": "Apple",
        "name": "苹果",
        "value": "APPLE"
      },
      {
        "key": "Banana",
        "name": "香蕉",
        "value": "BANANA",
        "disabled": true
      }
    ]
  },
  {
    "key": "Vegetable",
    "name": "蔬菜",
    "value": "VEGETABLE",
    "children": [
      {
        "key": "Carrot",
        "name": "胡萝卜",
        "value": "CARROT"
      }
    ]
  }
]

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL