Golang中JSON Unmarshal的用法解析

Golang中JSON Unmarshal的用法解析 我要做的事情:

我有些API返回JSON格式的数据。每个API的字段都不同,而我只需要少量数据。因此我为每个API准备了一些结构体,以筛选出我需要的数据。

遇到的问题:

由于存在许多不同的结构体,当我准备使用 json.Unmarshal 时,我不得不传递 interface{}。但当我使用 interface{} 时,我发现结果是 map,并且来自API的所有数据都在这个 map 中,这导致我为API准备的结构体(用于筛选某些数据)没有起作用。

例如:

type API struct {
    MyTarget    string    `json:"target"`
}

type API2 struct {
    MyTarget2    string    `json:"target2"`
}

type API3 struct {
    MyTarget3    string    `json:"target3"`
}

var APISlice = map[string]interface{}{
    "api":API{},
    "api2":API2{},
    "api3":API3{},
}

func main() {
    data = []byte{xxxxx}    //from API response
    str := "api1"                //assume it's api1,I will range every API when I really work
    myStruct := APISlice[str]
    json.Unmarshal(data,&myStruct)    
    //now the result is a map which include all the data from API.  sad.........
}

我真正想要的是数据能够被反序列化到结构体中,这样我就能获取到在结构体中定义的数据。

如何将数据反序列化到特定的结构体中(这可能吗?)? 或者我能否通过 json.Unmarshal(data,interface{}) 获取我想要的数据?


更多关于Golang中JSON Unmarshal的用法解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

我不太清楚您想做什么。也许您可以给我们一个具体的例子。

更多关于Golang中JSON Unmarshal的用法解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


哇!现在我明白关键所在了。非常感谢! 100分

你的第一个问题是,映射 APISlice 中没有键 “api1”,因此 myStruct 是一个 nil 的 interface{}。即使你将键改为 “api1”,myStruct 仍然是 interface{} 类型。你需要传递一个指向该结构体类型变量的指针。你可以像这里一样使用类型开关来解决它 Go Playground - The Go Programming Language

func main() {
    fmt.Println("hello world")
}

你好, 感谢你的解决方案,它运行得很好。 然而,我必须在每个 case 中都写 json.Unmarshal,就像这样:

	switch x := apiStruct.(type) {
	case APIA:
		json.Unmarshal(data,&x)
    case APIB:
		json.Unmarshal(data,&x)
    case APIN:
		json.Unmarshal(data,&x)
	default:
		fmt.Printf("API convert failed!\n")
		return
	}

我曾尝试在 switch 外部定义 x,因为我希望在 switch 外部使用它,但这没有成功。

    var x interface{}
	switch x = apiStruct.(type) {
	case APIA:
    case APIB:
    case APIN:
	default:
		fmt.Printf("API convert failed!\n")
		return
	}
    json.Unmarshal(data,&x)

所以我必须在 switch 内部进行 Unmarshal 并重复这个函数吗? 我只能在 switch 内部获取到特定的 API 结构体

好的,我明白了。在您的 processHttpResp 函数中,您有:

apiStruct := APIMap[apiName]

得到的 apiStruct 是一个类型为 interface{} 的变量,而这个 interface{} 内部包含的是一个 API1API2 的结构体值。&apiStruct 获取的是 interface{} 变量 的地址,而不是 interface{} 内部值 的地址,因此您最终得到的是 *interface{},而不是 *API1。当这个 &apiStruct 被传递给 json.Unmarshal 时,其效果与您写成以下代码没有区别:

var v interface{} = API1{}
err := json.Unmarshal(data, &v)  // 这就是为什么您会得到一个 map[string]interface{}

如果您将 APIMap 定义为:

var APIMap = map[string]interface{}{
	"api1": &API1{},
	"api2": &API2{},
}

然后在 processHttpResp 中,您这样写:

	apiStruct := APIMap[apiName]
	err := json.Unmarshal(data, apiStruct)  // 不再是 &apiStruct

那么它也能工作,但是您每次都会反序列化到同一个实际的结构体实例中,因此它不适合并发使用。

在我的示例中,我使用了 reflect 包来创建一个新的 *API1*API2 等。但如果您不介意 APIMap 写得稍微冗长一些,下面这种方法可能会更快:

var APIMap = map[string]func() interface{} {
    "api1": func() interface{} { return &API1{} },
    "api2": func() interface{} { return &API2{} },
}

// ...

func unmarshalAPIResp(key string, data []byte) (interface{}, error) {
	f, ok := APIMap[key]
	if !ok {
		return nil, errNotFound
	}
	v := f()
	if err := json.Unmarshal(data, v); err != nil {
		return nil, fmt.Errorf(
			"failed to unmarshal %d bytes into %T: %w",
			len(data), v, err,
		)
	}
	return v, nil
}

更具体地说:

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

// 我需要 "data" 字段
type API1 struct {
    Data []API1x `json:"data"`
}

type API1x struct {
    Name string `json:"name"`
}

// 我需要 "ip" 字段
type API2 struct {
    IP []string `json:"ip"`
}

var APIMap = map[string]interface{}{
	"api1": API1{},
	"api2": API2{},
}

func main() {

	type resp struct {
		apiName string
		apiData []byte
	}

	api1Data := `
    {
        "data":
            [
                {"name":"jack"},
                {"name":"tom"},
                {"name":"marry"}
            ],
        "other":{
            "other":"other"
        }
    }
    `
	api2Data := `
    {
        "other":{
            "other":"other"
        },
        "ip":["1.1.1.1","2.2.2.2"]
    }
    `

	var resp1 = resp{apiName: "api1", apiData: []byte(api1Data)}
	var resp2 = resp{apiName: "api2", apiData: []byte(api2Data)}

	//   ----------如果我这样编程就很愚蠢。
	//s1 := API1{}
	//s2 := API2{}
	//json.Unmarshal([]byte(api1Data),&s1)
	//json.Unmarshal([]byte(api2Data),&s2)

	var respSlice = []resp{resp1, resp2}
	for _, v := range respSlice {
        processHttpResp(v.apiName,v.apiData)
	}
}

func processHttpResp(apiName string, data []byte) {
	apiStruct := APIMap[apiName]
	err := json.Unmarshal(data, &apiStruct)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(apiStruct)
}

输出:

map[data:[map[name:jack] map[name:tom] map[name:marry]] other:map[other:other]]
map[ip:[1.1.1.1 2.2.2.2] other:map[other:other]]

问题:

  • 我需要 Struct 类型而不是 map,因为我需要使用 reflect 来处理这个结构体
  • 我只需要在 struct 中定义的字段,例如 API1(我只需要 “data” 字段,而不是全部)

在Go中,当使用interface{}作为json.Unmarshal的目标时,JSON包会默认将数据解析为map[string]interface{}。要解决这个问题,你需要使用类型断言或反射来明确指定目标结构体类型。以下是几种解决方案:

方法1:使用类型断言(推荐)

type API1 struct {
    MyTarget string `json:"target"`
}

type API2 struct {
    MyTarget2 string `json:"target2"`
}

func unmarshalWithTypeAssertion() {
    data := []byte(`{"target": "value1", "other": "ignored"}`)
    
    // 根据API类型选择不同的结构体
    var result interface{}
    apiType := "api1"
    
    switch apiType {
    case "api1":
        var api1 API1
        json.Unmarshal(data, &api1)
        result = api1
        fmt.Printf("API1: %+v\n", api1)
    case "api2":
        var api2 API2
        json.Unmarshal(data, &api2)
        result = api2
        fmt.Printf("API2: %+v\n", api2)
    }
}

方法2:使用函数映射

var unmarshalFuncs = map[string]func([]byte) (interface{}, error){
    "api1": func(data []byte) (interface{}, error) {
        var api1 API1
        err := json.Unmarshal(data, &api1)
        return api1, err
    },
    "api2": func(data []byte) (interface{}, error) {
        var api2 API2
        err := json.Unmarshal(data, &api2)
        return api2, err
    },
}

func main() {
    data := []byte(`{"target": "value1", "other": "ignored"}`)
    
    if unmarshalFunc, ok := unmarshalFuncs["api1"]; ok {
        result, err := unmarshalFunc(data)
        if err == nil {
            fmt.Printf("Result: %+v\n", result)
        }
    }
}

方法3:使用反射(更灵活)

var typeRegistry = map[string]reflect.Type{
    "api1": reflect.TypeOf(API1{}),
    "api2": reflect.TypeOf(API2{}),
}

func unmarshalWithReflection(apiType string, data []byte) (interface{}, error) {
    if t, ok := typeRegistry[apiType]; ok {
        v := reflect.New(t).Interface()
        err := json.Unmarshal(data, v)
        return reflect.ValueOf(v).Elem().Interface(), err
    }
    return nil, fmt.Errorf("type not found: %s", apiType)
}

func main() {
    data := []byte(`{"target": "value1", "other": "ignored"}`)
    
    result, err := unmarshalWithReflection("api1", data)
    if err == nil {
        fmt.Printf("Result: %+v\n", result)
        if api1, ok := result.(API1); ok {
            fmt.Printf("MyTarget: %s\n", api1.MyTarget)
        }
    }
}

方法4:使用泛型(Go 1.18+)

func unmarshalGeneric[T any](data []byte) (*T, error) {
    var result T
    err := json.Unmarshal(data, &result)
    return &result, err
}

func main() {
    data := []byte(`{"target": "value1", "other": "ignored"}`)
    
    // 明确指定类型
    api1, err := unmarshalGeneric[API1](data)
    if err == nil {
        fmt.Printf("API1: %+v\n", api1)
    }
}

修正你的原始代码

type API1 struct {
    MyTarget string `json:"target"`
}

type API2 struct {
    MyTarget2 string `json:"target2"`
}

var APISlice = map[string]interface{}{
    "api1": API1{},
    "api2": API2{},
}

func main() {
    data := []byte(`{"target": "value1", "other": "ignored"}`)
    apiKey := "api1"
    
    // 获取对应类型的零值,然后创建指针
    switch target := APISlice[apiKey].(type) {
    case API1:
        var result API1
        json.Unmarshal(data, &result)
        fmt.Printf("API1 result: %+v\n", result)
    case API2:
        var result API2
        json.Unmarshal(data, &result)
        fmt.Printf("API2 result: %+v\n", result)
    }
}

关键点:

  1. json.Unmarshal需要知道具体的类型信息
  2. 使用interface{}时,JSON包无法推断具体结构体类型
  3. 需要通过类型断言、反射或明确的类型声明来指定目标类型
  4. 结构体标签json:"field_name"仍然有效,只会解析标记的字段
回到顶部