Typecasting in Golang



我正在阅读以下文章: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是类型*LeaveTypereceiver函数。当包想要设置的变量类型为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.UnmashalerLeaveType.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类型的变量,但由于LTLeaveType是其基本类型,*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行,看看会发生什么。

最新更新