'Changing value of field in a go struct

I'm making an http request in golang to an external api. It gives a general response of {"error":[]string, "result":changing interface{}}. depending on the function that is making the request, the Result field changes. Since I know the structure of the Result field for each function I run, I want to be able to change the value of Result before unmarshalling to json. I've tried to do this with the following code:

func GetAssets(output *Resp, resultType interface{}) error {
    return publicRequest("/Assets", output, resultType)
}

func publicRequest(endPoint string, output *Resp, resultType interface{}) error {
    url := Rest_url + Pub_rest_url + endPoint //"https://api.kraken.com/0/public/Assets in this case
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    output.Result = resultType
    return json.NewDecoder(resp.Body).Decode(&output)
}

Here is how it's being ran in main

type Resp struct {
    Error  []string    `json:"error"`
    Result interface{} `json:"result"`
}

type AssetInfo struct {
    Aclass   string `json:"aclass"`
    Altname  string `json:"altname"`
    Decimals int    `json:"decimals"`
    Display  int    `json:"display_decimals"`
}

func main() {
    var result map[string]AssetInfo
    jsonData := Resp{}
    rest_api_client.GetAssets(&jsonData, result)
    fmt.Println(jsonData)
}

The issue is that it doesn't unmarshal correctly. A map is created for each asset, but the data contained inside of each asset is also being stored inside of a map. I'm not sure if I explained this well, but here is the current response after unmarshalling to understand what I mean.

Here is the data type of Resp.Result: map[string]interface {}

{[] map[1INCH:map[aclass:currency altname:1INCH decimals:10 display_decimals:5] AAVE:map[aclass:currency altname:AAVE decimals:10 display_decimals:5] ACA:map[aclass:currency altname:ACA decimals:10 display_decimals:5] ADA:map[aclass:currency altname:ADA decimals:8 display_decimals:6]...}

The response type I'm looking for is map[string]AssetInfo. Hopefully it could be unmarshalled like this:

{[] map[1INCH:{currency 1INCH 10 5} AAVE:{currency AAVE 10 5} ACA:{currency ACA 10 5} ADA:{currency ADA 8 6} ADA.S:{currency ADA.S 8 6}...}

Any help? I'd rather keep the Resp struct as generic as possible and just change the value of the Result field (if this is even possible to do correctly) since I plan to have multiple functions that call different endpoints of the api, and they'll all have the same underlying response type of the Resp struct with different Result types



Solution 1:[1]

You can view a working example in the following repo:

https://github.com/alessiosavi/GoArbitrage/blob/e107af466852b1ed30c2413eb4401595f7412b4f/markets/kraken/kraken.go

Basically, I've defined the following structure:

type Tickers struct {
    Error  []interface{}     `json:"error"`
    Result map[string]Ticker `json:"result"`
}

type Ticker struct {
    Aclass          string `json:"aclass"`
    Altname         string `json:"altname"`
    Decimals        int    `json:"decimals"`
    DisplayDecimals int    `json:"display_decimals"`
}

Than I execute the request in the following way:

const KRAKEN_TICKERS_URL string = `https://api.kraken.com/0/public/Assets`

type Kraken struct {
    PairsNames []string                                 `json:"pairs_name"`
    Pairs      map[string]datastructure.KrakenPair      `json:"pairs"`
    OrderBook  map[string]datastructure.KrakenOrderBook `json:"orderbook"`
    MakerFee   float64                                  `json:"maker_fee"`
    TakerFees  float64                                  `json:"taker_fee"`
    // FeePercent is delegated to save if the fee is in percent or in coin
    FeePercent bool `json:"fee_percent"`
    Tickers    []string
}


// Init is delegated to initialize the maps for the kraken
func (k *Kraken) Init() {
    k.Pairs = make(map[string]datastructure.KrakenPair)
    k.OrderBook = make(map[string]datastructure.KrakenOrderBook)
    k.SetFees()
}

// SetFees is delegated to initialize the fee type/amount for the given market
func (k *Kraken) SetFees() {
    k.MakerFee = 0.16
    k.TakerFees = 0.26
    k.FeePercent = true
}


func (k *Kraken) GetTickers() error {
    res := datastructure.Tickers{}
    var err error
    var request req.Request
    var data []byte
    var tickers []string
    resp := request.SendRequest(KRAKEN_TICKERS_URL, "GET", nil, nil, false, 10*time.Second)
    if resp.Error != nil {
        zap.S().Debugw("Error during http request. Err: " + resp.Error.Error())
        return resp.Error
    }
    if resp.StatusCode != 200 {
        zap.S().Warnw("Received a non 200 status code: " + strconv.Itoa(resp.StatusCode))
        return errors.New("NON_200_STATUS_CODE")
    }
    data = resp.Body
    if err = json.Unmarshal(data, &res); err != nil {
        zap.S().Warn("ERROR! :" + err.Error())
        return err
    }
    zap.S().Infof("Data: %v", res.Result)
    tickers = make([]string, len(res.Result))
    i := 0
    for key := range res.Result {
        tickers[i] = res.Result[key].Altname
        i++
    }
    k.Tickers = tickers
    return nil
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 alessiosavi