Golang中传递空接口导致类型变化的问题

Golang中传递空接口导致类型变化的问题 大家好

我正在编写一些代码来测试JSON反序列化。为了避免编写大量代码,我写了一个函数,你可以传入JSON、一个将作为指针传递给Go的Unmarshal方法的容器,以及我期望的结构体。

这样就不需要编写大量使用以下语法的测试:

有效的代码:

	var holderNull1 int32Holder
	jsonNull1 := "{}"
	if err := json.Unmarshal([]byte(jsonNull1), &holderNull1); err != nil {
		t.Error("Unexpected error ", err)
	}
	assertEq(holderNull1,int32Holder{Holder: NewInt32OptEmpty()}, t)

我写了这个辅助函数:

func testUnmarshal(jsonValue string, holder interface{}, expect interface{}, t *testing.T) {
	if err := json.Unmarshal([]byte(jsonValue), &holder); err != nil {
		t.Error("Unexpected error ", err)
	}
	println(holder)
	assertEq(holder,expect, t)
}

并这样调用它:

testUnmarshal("{}", int32Holder{}, int32Holder{Holder: NewInt32OptEmpty()}, t)

第二种方法显示,传入的容器类型是 map[string]interface {},所以将一个结构体传递给函数,然后将其作为指针传递似乎改变了它的类型。

我漏掉了什么?

我在Go Playground上整理了一个小例子:

https://play.golang.org/p/WYOyhrNEDS5


更多关于Golang中传递空接口导致类型变化的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

int2Holder 的类型定义是什么?代码是否可以在某处获取,以便我们尝试运行并复现问题?如果不行,您能否提供一个最小可复现的场景?

更多关于Golang中传递空接口导致类型变化的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我在 Go Playground 上整理了一个小例子:

https://play.golang.org/p/WYOyhrNEDS5

通过传递地址更新了代码。以下是可运行的版本:

https://play.golang.org/p/crbUdJG-E96

问题在于你的辅助函数中 holder 参数的类型声明和 json.Unmarshal 的调用方式。当你将 int32Holder{} 作为 interface{} 参数传递时,函数内部接收到的是该结构体的值拷贝。随后你对其取地址 &holder,实际上获取的是这个接口变量本身的地址,而非原始结构体的地址。json.Unmarshal 接收到一个指向 interface{} 的指针,它会在内部创建一个新的 map[string]interface{} 来解析 JSON,而不是填充你期望的结构体。

以下是修正后的代码示例:

func testUnmarshal(jsonValue string, holder interface{}, expect interface{}, t *testing.T) {
    // 通过反射获取 holder 的指针,确保 Unmarshal 能修改原始值
    holderPtr := reflect.ValueOf(holder).Addr().Interface()
    if err := json.Unmarshal([]byte(jsonValue), holderPtr); err != nil {
        t.Error("Unexpected error ", err)
    }
    // 比较时使用解引用后的值
    assertEq(reflect.ValueOf(holderPtr).Elem().Interface(), expect, t)
}

调用方式需要调整为传递结构体的指针:

var holder int32Holder
testUnmarshal("{}", &holder, int32Holder{Holder: NewInt32OptEmpty()}, t)

或者,更简洁的实现是直接要求调用者传递指针:

func testUnmarshal(jsonValue string, holderPtr interface{}, expect interface{}, t *testing.T) {
    if err := json.Unmarshal([]byte(jsonValue), holderPtr); err != nil {
        t.Error("Unexpected error ", err)
    }
    assertEq(holderPtr, expect, t)
}

调用方式:

var holder int32Holder
testUnmarshal("{}", &holder, int32Holder{Holder: NewInt32OptEmpty()}, t)

关键点:

  1. json.Unmarshal 必须接收一个指向目标类型的指针
  2. 当通过 interface{} 传递参数时,需要确保传递的是指针的接口,而不是值的接口
  3. 使用反射可以处理通用情况,但直接要求指针参数更清晰高效

你的 Playground 示例修正后:

func testUnmarshal(jsonValue string, holder interface{}, expect interface{}, t *testing.T) {
    holderPtr := reflect.ValueOf(holder).Addr().Interface()
    if err := json.Unmarshal([]byte(jsonValue), holderPtr); err != nil {
        t.Error("Unexpected error ", err)
    }
    actual := reflect.ValueOf(holderPtr).Elem().Interface()
    if !reflect.DeepEqual(actual, expect) {
        t.Errorf("Expected %v, got %v", expect, actual)
    }
}
回到顶部