About json Unmarshal

What am I going to do :

I have some API which response Json format data.Fields in every API are different and I just need a few data.So I prepared some struct for every API to filter out the data I need.

What’s the issue:

Due to many different struct‘s existence,When I was ready to use json.Unmarshal,I had to pass interface.But when I using interface,I found the result is map,and all of the data from the API are in the map,which caused my struct for API (filter some data) did’t work.

For instance:

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.........
}

What I really want is the data can be unmarshal into the struct so that I can get the data I have defined in struct.

How to unmarshal data into the specific struct (Is it possiable?)?
Or can I get the data I want through json.Unmarshal(data,interface)?

I am unclear on what you are trying to do. Maybe you can give us a concrete example.

Your first problem is that the map APISlice has no key “api1”, so myStruct is a nil interface{}. And even if you change the key to “api1”, then myStruct is still of type interface{}. You need to pass a pointer to a variable of the struct type. You can solve it with a type switch like here Go Playground - The Go Programming Language.

Hi,

More specificlly:

package main

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

//I need "data" field
type API1 struct {
    Data []API1x `json:"data"`
}

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

//I need "ip" field
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)}

	//   ----------It's stupid if I program like this.
	//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)
}

output:

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]]

issues:

  • I need Struct type rather than map because I need to use reflect to process this struct
  • I just need the field that I have defined in the struct,such as API1 (I need “data” field only,not all of them)

Hi,
Thanks for your solution and it works well.
However, I have to write json.Unmarshal in every case,like this

	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
	}

I have tried to define x outside the switch because I wanna use it outside the switch,but it did’t work.

    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)

So I have to Unmarshal inside the switch and repeat the function ? :disappointed_relieved:
I can only get specific API struct inside the switch

OK, I think I understand what you’re going for now. How about this: Go Playground - The Go Programming Language

In your processHttpResp function, you have:

apiStruct := APIMap[apiName]

The resulting apiStruct is a variable with type interface{} and inside of that interface{} is an API1 or API2 struct value. &apiStruct takes the address of the interface{} variable, not of the value inside the interface{} so you end up with *interface{} and not a *API1. When that &apiStruct is passed to json.Unmarshal, it’s no different than if you wrote:

var v interface{} = API1{}
err := json.Unmarshal(data, &v)  // this is why you get a map[string]interface{}

If you instead defined your APIMap as:

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

And then in processHttpResp, you had:

	apiStruct := APIMap[apiName]
	err := json.Unmarshal(data, apiStruct)  // not &apiStruct any more

Then it would also work *but* you would unmarshal into the same actual struct every time, so it’s not safe for concurrent use.

In my example, I used the reflect package to create a new *API1, *API2, etc., but if you’re OK with APIMap being more verbose, this would probably be faster:

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
}
3 Likes

Wow! Right now I understand what’s the point. Thanks so much! :100: