我正在阅读以下文章:https://www.ribice.ba/golang-enums/
其中一个代码示例中定义了一个函数:
func (lt *LeaveType) UnmarshalJSON(b []byte) error {
// Define a secondary type to avoid ending up with a recursive call to json.Unmarshal
type LT LeaveType;
var r *LT = (*LT)(lt);
err := json.Unmarshal(b, &r)
if err != nil{
panic(err)
}
switch *lt {
case AnnualLeave, Sick, BankHoliday, Other:
return nil
}
return errors.New("Inalid leave type")
}
语法var r *LT = (*LT)(lt);
在这个例子中做什么?
Go技术上没有强制转换,而是有转换。显式转换的语法是T(x)
,其中T
是某种类型,x
是可转换为该类型的某种值。有关详细信息,请参阅Go规范中的Conversions。
正如您从函数声明中看到的:
func (lt *LeaveType) UnmarshalJSON(b []byte) error {
lt
本身具有指向LeaveType
的类型指针,而UnmarshalJSON
是类型*LeaveType
的receiver函数。当包想要设置的变量类型为LeaveType
(或*LeaveType
——在这种情况下,包将自己创建LeaveType
变量)时,encoding/json
包将调用这样一个函数来解码输入JSON。
正如代码中的注释所说,代码的作者现在希望encoding/json
代码对JSON进行解组,就好像没有函数UnmarshalJSON
一样。但是是一个函数UnmarshalJSON
,所以如果我们只是调用encoding/json
代码而不使用任何技巧,encoding/json
就会再次调用这个函数,从而导致无限递归。
通过定义一个内容与现有类型LeaveType
完全相同的新类型LT
,我们最终得到了一个不具有接收器功能的新类型。在该类型(或指向该类型的指针)的实例上调用encoding/json
不会调用*LeaveType
接收器,因为LT
是不同的类型,即使其内容完全匹配。
我们可以这样做:
func (lt *LeaveType) UnmarshalJSON(b []byte) error {
type LT LeaveType
var r LT
err := json.Unmarshal(b, &r)
if err != nil {
panic(err)
}
// ...
}
这将填充r
,其大小和形状与任何LeaveType
变量相同。然后我们可以使用填充的r
来设置*lt
:
*lt = LeaveType(r) // an ordinary conversion
之后我们可以像以前一样,使用*lt
作为值。但这意味着UnmarshalJSON
必须设置一个临时变量r
,然后我们必须将其复制到其最终目的地。为什么不设置一些内容,使UnmarshalJSON
填充目标变量,但使用我们选择的类型呢?
这就是的语法。这不是最短的版本:正如Cerise Limón所指出的,有一种更短的拼写方式(通常更喜欢更短的拼写)。(*LT)(lt)
中的第一组括号是将*
(指向部分的指针)绑定到LT
所必需的,因为*LT(lt)
的绑定错误:它与*(LT(lt))
的含义相同,这不是我们想要的。
表达式(*LT)(lt)
是到类型*LT
的转换。
语句var r *LT = (*LT)(lt);
将变量r
声明为具有初始值(*LT)(lt)
的类型*LT
。该语句可以更简单地写成r := (*LT)(lt)
。不需要两次提及该类型,也不需要以分号结束该行。
该函数使用空方法集声明类型LT
,以避免对UnMarshalJSON
的递归调用。
json.Unmarshal()
将一些JSON文本解组为Go值。如果要解组为的值实现了json.Unmarshaler
接口,则调用其UnmarshalJSON()
方法,该方法允许实现自定义解组逻辑。
引用json.Unmarshal()
:
要将JSON解组为实现unmarshal接口的值,Unmarshall会调用该值的UnmarshalJSON方法,包括当输入为JSON null时。
json.Unmarshaler
接口:
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
LeaveType
(或者更具体地说,*LeaveType
)有一个UnmarshalJSON()
方法,我们可以在问题中看到,所以它实现了json.Unmarshaler
。
LeaveType.UnmarshalJSON()
方法希望使用默认解组逻辑来完成"硬"部分,只想进行一些最终调整。所以它称json.Unmarshal()
:
err := json.Unmarshal(b, &r)
如果我们将lt
传递到unmarshal中,由于lt
实现了json.Unmashaler
,LeaveType.UnmarshalJSON()
将由json
包调用,从而有效地导致无限的"递归"。
当然,这不是我们想要的。为了避免无限递归,我们必须传递一个不实现json.Unmarshaler
的值,该值的类型没有UnmarshalJSON()
方法。
这就是创建一种新类型的原因:
type LT LeaveType
type
关键字创建了一个名为LT
的新类型,它不同于LeaveType
。它不"继承"LeaveType
的任何方法,因此LT
不实现json.Unmarshaler
。因此,如果我们将值LT
或*LT
传递给json.Unmarshal()
,则不会导致LeaveType.UnmarshalJSON()
被调用(由json
包调用)。
var r *LT = (*LT)(lt)
这声明了一个名为r
的变量,其类型为*LT
。并将转换后的值CCD_ 76分配给CCD_。需要转换,因为lt
的类型是*LeaveType
,因此它不能分配给*LT
类型的变量,但由于LT
的LeaveType
是其基本类型,*LeaveType
可以转换为*LT
。
所以r
是一个指针,它指向与lt
相同的值,它具有相同的内存布局。因此,如果我们使用默认的解组逻辑并"填充"r
指向的结构,那么将填充lt
指向的"相同"结构。
请参阅相关/类似问题:调用json。UnmarshalJSON函数内部的Unmarshal不会导致堆栈溢出
它将lt
(一个LeaveType
指针)转换为LT
指针。
CCD_ 92被CCD_ 93定义为等同于CCD_。
它这样做是出于评论中解释的原因。
// Define a secondary type to avoid ending up with a recursive call to json.Unmarshal
我不知道这是否有效或必要。
您可以在一个简单的Stringer
接口示例中看到相同的效果,其中fmt.Println
函数将尝试将数据封送为string
格式。如果给定值的类型具有String()
方法,则将优先使用它而不是反射。
此实现失败(并发出警告),因为它会导致无限递归:
type mystring string
func (ms mystring) String() string {
return fmt.Sprintf("mystring: %s", ms)
}
这个版本对于原始代码的作用至关重要:
type mystring2 string
func (ms mystring2) String() string {
type mystring2 string // <- local type mystring2 overrides global type
v := mystring2(ms)
return fmt.Sprintf("mystring2: %s", v)
}
拆下type mystring2 string
行,看看会发生什么。