How to declare the Type of a variable dynamically in GO, using if-else-like conditions?

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

How to declare the Type of a variable dynamically in GO, using if-else-like conditions?

问题

给定两种类型:

type A struct {
	ID      string
	Content []int
}

type B struct {
	ID      string
	Content map[string][]int
}

我需要一个函数根据条件告诉我以后要使用哪种类型(目的是正确地解析一个 JSON 字符串)。我想要一个像这样的函数:

func assign_typed_data(header string) interface{} {
	switch header {
	case "A":
		d := new(A)
		fmt.Printf("inner type is %T \n", *d) // inner type is A
		return *d
	case "B":
		d := new(B)
		fmt.Printf("inner type is %T \n", *d) // inner type is B
		return *d
	default:
	}
}

在外部代码中,我可以调用它并像下面这样解析 JSON,但返回的值变成了 "map[string]interface{}"。

header := "A"
data := assign_typed_data(header)
fmt.Printf("outer type is %T \n", data) // outer type is map[string]interface{}
json.Unmarshal(json_data, &data)

我还尝试了直接在外部代码中使用简单的 if-else 语句,而不调用函数,像下面这样,但也失败了,因为定义的作用域是局部的。

if header == "A" {
    data := *new(A)
} else if header == "B" {
    data := *new(B)
}
json.Unmarshal(json_data, &data)

有没有可能在 Go 中实现这个目标?

英文:

Given two types

type A struct {
	ID         string
	Content    []int
}

and

type B struct {
	ID         string
	Content    map[string][]int
}

and I need a function to tell me which type to use hereafter according to conditions (the purpose is to unmarshal a json string properly). I want a function like

func assign_typed_data(header string) interface{} {
    switch header {
    case "A" :
         d := new(A)
         fmt.Printf('inner type is %T \n', *d)      // inner type is A
         return *d
    case "B" :
         d := new(B)
         fmt.Printf('inner type is %T \n', *d)      // inner type is B
         return *d
    default:
    }
}

and in the outer code I can call it and unmarshal the json like follows, but the returned value turns to "map[string]interface{}".

header := "A"
data := assign_typed_data(header)
fmt.Printf('outter type is %T \n', data)      // outter type is map[string]interface{}
json.Unmarshal(json_data, &data)

I also tried simple if-else statements in the outter code directly without calling a function, like follows, but failed as well because of defined scope is local.

if header == "A" {
    data := *new(A)
}else if header == "B" {
    data := *new(B)
}
json.Unmarshal(json_data, &data)

Is there a possible way to achieve this goal in GO?

答案1

得分: 3

你需要将期望的数据类型的指针传递给json.Unmarshal()函数。也就是说,传递*A*B

然而,assign_typed_data()函数返回的是interface{}类型,而你取得了它的地址,所以你将传递*interface{}

assign_typed_data()函数修改为返回指针值*A*B,并将data直接传递给json.Unmarshal(),因为它已经保存了指针值:

func createValue(header string) interface{} {
    switch header {
    case "A":
        d := new(A)
        fmt.Printf("inner type is %T \n", d) // inner type is *A
        return d
    case "B":
        d := new(B)
        fmt.Printf("inner type is %T \n", d) // inner type is *B
        return d
    default:
        return nil
    }
}

Testing it:

s := `{"ID":"abc","Content":[1,2,3]}`
data := createValue("A")
if err := json.Unmarshal([]byte(s), data); err != nil {
    panic(err)
}
fmt.Printf("outer type is %T \n", data)
fmt.Printf("outer value is %+v \n", data)

s = `{"ID":"abc","Content":{"one":[1,2], "two":[3,4]}}`
data = createValue("B")
if err := json.Unmarshal([]byte(s), data); err != nil {
    panic(err)
}
fmt.Printf("outer type is %T \n", data)
fmt.Printf("outer value is %+v \n", data)

输出结果为:

inner type is *main.A 
outer type is *main.A 
outer value is &{ID:abc Content:[1 2 3]} 
inner type is *main.B 
outer type is *main.B 
outer value is &{ID:abc Content:map[one:[1 2] two:[3 4]]} 

请查看相关的/可能重复的问题,以进一步了解该问题:

https://stackoverflow.com/questions/38829941/golang-interface-type-misunderstanding/38830638#38830638

https://stackoverflow.com/questions/52970019/is-it-possible-to-dynamically-set-the-output-of-json-unmarshal/52970254#52970254

https://stackoverflow.com/questions/44779319/how-to-tell-json-unmarshal-to-use-struct-instead-of-interface/44779409#44779409

英文:

You have to pass a pointer to the expected data type to json.Unmarshal(). That is, *A or *B.

Yet, assign_typed_data() returns interface{} and you take its address, so you'll be passing *interface{}.

Change assign_typed_data() to return the pointer value *A or *B, and pass data as-is to json.Unmarshal() as it will already hold a pointer value:

func createValue(header string) interface{} {
	switch header {
	case "A":
		d := new(A)
		fmt.Printf("inner type is %T \n", d) // inner type is *A
		return d
	case "B":
		d := new(B)
		fmt.Printf("inner type is %T \n", d) // inner type is *B
		return d
	default:
		return nil
	}
}

Testing it:

s := `{"ID":"abc","Content":[1,2,3]}`
data := createValue("A")
if err := json.Unmarshal([]byte(s), data); err != nil {
	panic(err)
}
fmt.Printf("outer type is %T \n", data)
fmt.Printf("outer value is %+v \n", data)

s = `{"ID":"abc","Content":{"one":[1,2], "two":[3,4]}}`
data = createValue("B")
if err := json.Unmarshal([]byte(s), data); err != nil {
	panic(err)
}
fmt.Printf("outer type is %T \n", data)
fmt.Printf("outer value is %+v \n", data)

Which outputs (try it on the Go Playground):

inner type is *main.A 
outer type is *main.A 
outer value is &{ID:abc Content:[1 2 3]} 
inner type is *main.B 
outer type is *main.B 
outer value is &{ID:abc Content:map[one:[1 2] two:[3 4]]} 

Please check related / possible duplicates which detail the issue further:

https://stackoverflow.com/questions/38829941/golang-interface-type-misunderstanding/38830638#38830638

https://stackoverflow.com/questions/52970019/is-it-possible-to-dynamically-set-the-output-of-json-unmarshal/52970254#52970254

https://stackoverflow.com/questions/44779319/how-to-tell-json-unmarshal-to-use-struct-instead-of-interface/44779409#44779409

huangapple
  • 本文由 发表于 2021年6月26日 18:40:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/68141506.html
匿名

发表评论

匿名网友

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

确定