【问题标题】:Typecasting in GolangGolang 中的类型转换
【发布时间】:2020-06-11 21:49:57
【问题描述】:

我正在阅读以下文章: 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);的语法是什么?

【问题讨论】:

    标签: json pointers go syntax type-conversion


    【解决方案1】:

    Go 在技术上没有 casts 而是 conversions。显式转换的语法是T(x),其中T 是某种类型,x 是可转换为该类型的某个值。详情请见Conversions in the Go specification

    从函数的声明中可以看出:

    func (lt *LeaveType) UnmarshalJSON(b []byte) error {
    

    lt 本身具有类型指向LeaveType 的指针,而UnmarshalJSON 是类型*LeaveType接收器函数。当包想要设置的变量类型为LeaveType(或*LeaveType——在这种情况下,包将自己创建LeaveType变量)时,encoding/json 包将调用这样的函数来解码输入 JSON。

    正如代码中的注释所说,代码作者现在希望让encoding/json 代码解组 JSON就好像没有函数 UnmarshalJSON。但是一个函数UnmarshalJSON,所以如果我们只是调用encoding/json代码而不加一点技巧,encoding/json会再次调用这个函数,导致无限递归。 p>

    通过定义一个 new 类型 LT 其内容与现有类型 LeaveType 完全相同,我们最终得到了一个 没有 的新类型一个接收函数。在此类型(或指向此类型的指针)的实例上调用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 填充目标变量,但使用我们选择的类型?

    这就是这里的语法for。这不是 最短 版本:正如 Cerise Limón 所指出的,有一种更短的拼写方式(通常首选更短的拼写)。 (*LT)(lt) 中的第一组括号需要将*指向部分的指针)绑定到LT,因为*LT(lt) 的绑定错误:它的含义相同*(LT(lt)) 这不是我们想要的。

    【讨论】:

    • 太棒了!感谢您提供详细的细分。我只是在学习 Go 并试图将我的头脑围绕在语法上。我对类型断言(使用点语法)和类型转换感到困惑。指针部分周围的括号也有意义!
    【解决方案2】:

    表达式(*LT)(lt)conversion 以键入*LT

    语句var r *LT = (*LT)(lt); 将变量r 声明为类型*LT,初始值为(*LT)(lt)。该语句可以更简单地写为r := (*LT)(lt)。无需两次提及类型或以分号结束行。

    函数声明类型 LT 并设置空方法以避免递归调用 UnMarshalJSON

    【讨论】:

      【解决方案3】:

      json.Unmarshal() 将一些 JSON 文本解组为 Go 值。如果要解组的值实现了json.Unmarshaler 接口,则调用其UnmarshalJSON() 方法,该方法允许实现自定义解组逻辑。

      引用json.Unmarshal():

      为了将 JSON 解组为实现 Unmarshaler 接口的值,Unmarshal 调用该值的 UnmarshalJSON 方法,包括当输入为 JSON null 时。

      json.Unmarshaler 接口:

      type Unmarshaler interface {
          UnmarshalJSON([]byte) error
      }
      

      LeaveType(或更具体地说是*LeaveType)有一个UnmarshalJSON() 方法,我们可以在问题中看到,所以它实现了json.Unmarshaler

      LeaveType.UnmarshalJSON() 方法希望使用 default 解组逻辑,该逻辑执行“硬”部分,并且只想进行一些最终调整。所以它调用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。并将值lt 转换为*LT。需要进行转换,因为lt 的类型为*LeaveType,因此不能将其分配给*LT 类型的变量,但由于LT 具有LeaveType 作为其基础类型,*LeaveType 可以转换为@ 987654368@.

      所以r 是一个指针,它指向与lt 相同的值,它具有相同的内存布局。因此,如果我们使用默认的解组逻辑并“填充”r 指向的结构,那么将填充lt 指向的“相同”结构。

      查看相关/类似问题:Call json.Unmarshal inside UnmarshalJSON function without causing stack overflow

      【讨论】:

        【解决方案4】:

        它将lt(一个LeaveType 指针)转换为一个LT 指针。

        LT 在上面由type LT LeaveType; 定义,等同于LeaveType

        出于评论中解释的原因,这样做。

        // Define a secondary type to avoid ending up with a recursive call to json.Unmarshal
        

        这是否有效或必要,我不知道。

        【讨论】:

        • 嵌套类型有效/必要。请参阅 Stringer 接口 here 的等效问题。
        【解决方案5】:

        您可以通过简单的Stringer 接口示例看到相同的效果,其中fmt.Println 函数将尝试将数据编组为string 格式。如果给定值的类型有String() 方法,它将优先使用反射。

        此实现失败(并且 go vet 发出警告),因为它导致无限递归:

        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 行和see what happens

        【讨论】:

          猜你喜欢
          • 2018-12-06
          • 2022-01-06
          • 2014-01-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-12-02
          • 2017-06-21
          • 1970-01-01
          相关资源
          最近更新 更多