如何在Go中使用枚举作为类型?

huangapple go评论72阅读模式
英文:

Go: How to use enum as a type?

问题

我有一个事件列表(枚举),用于定义特定的事件:

package events

const (
	NEW_USER       = "NEW_USER"
	DIRECT_MESSAGE = "DIRECT_MESSAGE"
	DISCONNECT     = "DISCONNECT"
)

还有一个结构体,它将使用这个枚举作为其属性之一:

type ConnectionPayload struct {
	EventName    string      `json:"eventName"`
	EventPayload interface{} `json:"eventPayload"`
}

有没有办法我可以将enum作为EventName的类型,而不是字符串?

typescript中是可能的,但不确定如何在golang中实现。

我希望开发人员强制使用枚举来正确使用事件名称,而不是通过使用任意随机字符串来犯错误。

英文:

I have a list of events (enum) which defines the particular event:

package events

const (
	NEW_USER       = "NEW_USER"
	DIRECT_MESSAGE = "DIRECT_MESSAGE"
	DISCONNECT     = "DISCONNECT"
)

And there is a struct that will use this enum as one of its attribute

type ConnectionPayload struct {
	EventName    string      `json:"eventName"`
	EventPayload interface{} `json:"eventPayload"`
}

Is there a way I can use enum as a type for EventName instead of string?

This is possible in typescript not sure how to do it in golang.

I want the developers to forcefully use correct event name using the enum instead of making a mistake by using any random string for eventname.

答案1

得分: 13

目前在Go语言中没有枚举类型,并且目前没有直接的方法来强制执行与TypeScript相同的规则。

在Go语言中,常见的做法是使用@ttrasn发布的建议:

定义一个自定义类型,并使用“枚举”值定义类型化的常量:

type EventName string

const (
    NEW_USER       EventName = "NEW_USER"
    DIRECT_MESSAGE EventName = "DIRECT_MESSAGE"
    DISCONNECT     EventName = "DISCONNECT"
)

这样可以在Go代码中标记期望此类值的位置:

// 示例函数签名:
func OnEvent(e EventName, id int) error { ... }

// 示例结构体:
type ConnectionPayload struct {
    EventName    EventName  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}

这将防止将普通的string赋值给EventName

var str string = "foo"
var ev EventName

ev = str // 无法编译通过
OnEvent(str, 42) // 无法编译通过

已知的限制有:

  • 在Go语言中,总是有一个零值:
    var ev EventName  // ev 是 ""
    
  • 字符串字面量与类型化变量不同,并且可以被赋值:
    var ev EventName = "SOMETHING_ELSE"
    
  • 允许类型转换:
    var str string = "foo"
    var ev EventName = EventName(str)
    
  • 在解组时没有检查:
    jsn := []byte(`{"eventName":"SOMETHING_ELSE","eventPayload":"some message"}`)
    err := json.Unmarshal(jsn, &payload) // 没有错误
    

如果您想要更严格的检查,您需要自己编写验证器或自定义解组器。

https://go.dev/play/p/vMUTpvH8DBb

英文:

There is no enum type at the moment in go, and there currently isn't a direct way to enforce the same rules as what typescript does.


A common practice in go is to use the suggestion posted by @ttrasn :

define a custom type, and typed constants with your "enum" values :

type EventName string

const (
    NEW_USER       EventName = "NEW_USER"
    DIRECT_MESSAGE EventName = "DIRECT_MESSAGE"
    DISCONNECT     EventName = "DISCONNECT"
)

This allows you to flag, in your go code, the places where you expect such a value :

// example function signature :
func OnEvent(e EventName, id int) error { ... }

// example struct :
type ConnectionPayload struct {
    EventName    EventName  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}

and it will prevent assigning a plain string to an EventName

var str string = "foo"
var ev EventName

ev = str // won't compile
OnEvent(str, 42) // won't compile

The known limitations are :

  • in go, there is always a zero value :
    var ev EventName  // ev is ""
    
  • string litterals are not the same as typed variables, and can be assigned :
    var ev EventName = "SOMETHING_ELSE"
    
  • casting is allowed :
    var str string = "foo"
    var ev EventName = EventName(str)
    
  • there is no check on unmarshalling :
    jsn := []byte(`{"eventName":"SOMETHING_ELSE","eventPayload":"some message"}`)
    err := json.Unmarshal(jsn, &payload) // no error
    

https://go.dev/play/p/vMUTpvH8DBb

If you want some stricter checking, you would have to write a validator or a custom unmarshaler yourself.

答案2

得分: 8

你可以通过生成以下代码来实现。

type EventNames string

const (
	NEW_USER       EventNames = "NEW_USER"
	DIRECT_MESSAGE EventNames = "DIRECT_MESSAGE"
	DISCONNECT     EventNames = "DISCONNECT"
)

type ConnectionPayload struct {
    EventName    EventNames  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}

然后将你的结构体更改为以下形式:

type ConnectionPayload struct {
    EventName    EventNames  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}
英文:

You can do it by generating code like the below.


type EventNames string

const (
	NEW_USER       EventNames = "NEW_USER"
	DIRECT_MESSAGE EventNames = "DIRECT_MESSAGE"
	DISCONNECT     EventNames = "DISCONNECT"
)

then change your struct to this:

type ConnectionPayload struct {
    EventName    EventNames  `json:"eventName"`
    EventPayload interface{} `json:"eventPayload"`
}

答案3

得分: 3

试试这个:

package main

import (
	"fmt"

	"gopkg.in/go-playground/validator.v9"
)

type EventName string

const (
	NEW_USER       EventName = "NEW_USER"
	DIRECT_MESSAGE EventName = "DIRECT_MESSAGE"
	DISCONNECT     EventName = "DISCONNECT"
)

type ConnectionPayload struct {
	EventName    EventName   `json:"eventName" validate:"oneof=NEW_USER DIRECT_MESSAGE DISCONNECT"`
	EventPayload interface{} `json:"eventPayload"`
}

func (s *ConnectionPayload) Validate() error {
	validate := validator.New()
	return validate.Struct(s)
}

func main() {
	a := ConnectionPayload{
		EventName: "NEW_USER",
	}
	b := ConnectionPayload{
		EventName: "NEW_USERR",
	}
	err := a.Validate()
	fmt.Println(a, err)
	err = b.Validate()
	fmt.Println(b, err)
}
英文:

Try this:

package main

import (
	"fmt"

	"gopkg.in/go-playground/validator.v9"
)

type EventName string

const (
	NEW_USER       EventName = "NEW_USER"
	DIRECT_MESSAGE EventName = "DIRECT_MESSAGE"
	DISCONNECT     EventName = "DISCONNECT"
)

type ConnectionPayload struct {
	EventName    EventName   `json:"eventName" validate:"oneof=NEW_USER DIRECT_MESSAGE DISCONNECT"`
	EventPayload interface{} `json:"eventPayload"`
}

func (s *ConnectionPayload) Validate() error {
	validate := validator.New()
	return validate.Struct(s)
}

func main() {
	a := ConnectionPayload{
		EventName: "NEW_USER",
	}
	b := ConnectionPayload{
		EventName: "NEW_USERR",
	}
	err := a.Validate()
	fmt.Println(a, err)
	err = b.Validate()
	fmt.Println(b, err)
}

答案4

得分: 0

这可能有点超出范围,但我在问题底部搜索到了OP提到的内容:

> 我希望开发人员强制使用正确的事件名称。

但是在研究过程中,我发现强制封闭它并不是应该做的事情。

免责声明
在Go中,不应该对枚举类型进行此操作。Go教会了我,在编程时应该负责任,即使你被允许做某事,比如创建枚举的新实例,也不应该这样做。对于这种特定情况,最好使用@ttrasn提出的解决方案。

但是重点是... Go允许您通过创建具有内部、不可导出方法的公共导出接口类型来封闭接口。该接口被封闭,因为只有一个包能够实现它,而该包就是声明该接口的包。

type EventName interface {
    eventName_internal()
}


type eventName string
func (eventName) eventName_internal() {}

const (
    NEW_USER       eventName = "NEW_USER"
    DIRECT_MESSAGE eventName = "DIRECT_MESSAGE"
    DISCONNECT     eventName = "DISCONNECT"
)

但是,与其将此模式用于枚举,通常更常用于当您需要接受特定接口的有限实现集时。尽管这是一个很好的示例,展示了如何封闭类型并具有与其他编程语言相同的功能。

英文:

This maybe little bit out-of-scope but I was searching for something OP mentioned at the bottom of the question:

> I want the developers to forcefully use correct event name.

But during research I found out that forcefully sealing it is not something that should be done.

DISCLAIMER:
This should not be done for enum types in GO. GO taught me that you should be responsible when you program and you shouldn't do something even if you're allowed to like create new instance of enum. It is far better to use solution @ttrasn proposed for this specific case.

But to the point... Go allows you to seal an interface by creating public, exported interface type with internal, non-exportable method. This interface is sealed because there is only single package that is able to implement it and that package is the one where the interface is declared.

type EventName interface {
    eventName_internal()
}


type eventName string
func (eventName) eventName_internal() {}

const (
    NEW_USER       eventName = "NEW_USER"
    DIRECT_MESSAGE eventName = "DIRECT_MESSAGE"
    DISCONNECT     eventName = "DISCONNECT"
)

But instead of using this pattern for enums this pattern is usually used for when you need to accept finite set of implementation for specific interface. Although this is great example to show how to seal a type and having same functionality as from other programming languages.

huangapple
  • 本文由 发表于 2022年4月20日 12:22:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/71934060.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定