在golang中解析JSON

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

Unmarshaling JSON in golang

问题

我正在尝试解决一个很棘手的问题。我想要对一个相当简单的数据进行解组,但不幸的是,出现了很多问题。

这是我想要解组的响应:

{"error":[],"result":{"XXBTZUSD":[[1647365820,"39192.0","39192.0","39191.9","39191.9","39191.9","0.18008008",10],[1647365880,"39186.1","39186.1","39172.0","39176.0","39174.4","0.13120077",10]],"last":1647408900}}

我编写了以下结构体来帮助解组:

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

type Trades struct {
	Pair	[]OHLC			`json:"XXBTZUSD"`
	Last	float64			`json:"last"`
}

type OHLC struct {
	Time	float64
	Open	string
	High	string
	Low		string
	Close	string
	Vwa	    string
	Volume	string
	Count	float64
}

我有一个函数调用,用于发起 HTTP 请求并解组数据。但不知何故,当 Pair 类型为 []OHLC 或 []*OHLC 时,我的代码会在函数调用之前就结束了。如果我将 Pair 类型更改为 interface{},那么代码就可以运行。但我仍然希望能够使用 OHLC 结构体。以下是完整的代码:

package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"encoding/json"
)

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

type Trades struct {
	Pair	[]OHLC			`json:"XXBTZUSD"`
	Last	float64			`json:"last"`
}

type OHLC struct {
	TT		float64
	Open	string
	High	string
	Low		string
	Close	string
	Vwap	string
	Volume	string
	Count	float64
}

func main() {
	fmt.Println("in main")
	getOhlc()
}

func getOhlc() {
	fmt.Println("in ohlc func")
	resp, err := http.Get("https://api.kraken.com/0/public/OHLC?pair=XXBTZUSD")
	if err != nil {
		fmt.Errorf("error after request")
		return
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Errorf("error when reading")
		return
	}

	var jsonData Resp
	err = json.Unmarshal(body, &jsonData)
	if err != nil {
		fmt.Errorf("error when unmarshalling")
		return
	}

	if len(jsonData.Error) > 0 {
		fmt.Errorf("error")
		return
	}

	fmt.Println(jsonData)
}

有关可能发生的原因,你有什么想法吗?

英文:

i'm having a lot of trouble getting my program to work. I want to unmarshal something pretty simple, but it's giving me a lot of issues, unfortunately.

Here is the response that I want to unmarshal:

{"error":[],"result":{"XXBTZUSD":[[1647365820,"39192.0","39192.0","39191.9","39191.9","39191.9","0.18008008",10],[1647365880,"39186.1","39186.1","39172.0","39176.0","39174.4","0.13120077",10]],"last":1647408900}}

I've wrote these structs to help with unmarshalling

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

type Trades struct {
	Pair	[]OHLC			`json:"XXBTZUSD"`
	Last	float64			`json:"last"`
}

type OHLC struct {
	Time	float64
	Open	string
	High	string
	Low		string
	Close	string
	Vwa	    string
	Volume	string
	Count	float64
}

I have a function call that makes the http request and then unmarshals the data. For whatever reason, my code will end before even starting the function call for the http request and subsequent unmarshalling when the Pair type is []OHLC or []*OHLC. If I change the Pair type to interface{}, then it runs. i want to make it work with the OHLC struct instead though. Below is the complete code:

package main

import (
	"fmt"
	"net/http"
	//"strings"
	"io/ioutil"
	"encoding/json"
)

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

type Trades struct {
	Pair	[]OHLC			`json:"XXBTZUSD"`
	Last	float64			`json:"last"`
}

type OHLC struct {
	TT		float64
	Open	string
	High	string
	Low		string
	Close	string
	Vwap	string
	Volume	string
	Count	float64
}

/*func main() {
	var data = [...]Trade{
		Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
		Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
	}
}*/



func main() {
	fmt.Println("in main");
	getOhlc()

}

func getOhlc() {
	fmt.Println("in ohlc func")
	resp, err := http.Get("https://api.kraken.com/0/public/OHLC?pair=XXBTZUSD");
	if err != nil {
		fmt.Errorf("error after request")
		return;
	}

	defer resp.Body.Close();

	body, err := ioutil.ReadAll(resp.Body);
	if err != nil {
		fmt.Errorf("error when reading")
		return;
	}

	var jsonData Resp;
	err = json.Unmarshal(body, &jsonData);
	if err != nil {
		fmt.Errorf("error when unmarshalling")
		return
	}

	if(len(jsonData.Error) > 0) {
		fmt.Errorf("error");
		return;
	}
	
	fmt.Println(jsonData);
}

Any ideas about what might be happening?

答案1

得分: 3

“有关可能发生的情况有什么想法吗?”

"XXBTZUSD" JSON数组中的元素本身也是数组,即"XXBTZUSD"是一个数组的数组。OHLC类型是一个结构体类型。标准库本身不会将JSON数组解组为Go结构体。Go结构体可用于解组JSON对象。JSON数组可以解组为Go切片或数组。

如果您只是打印json.Unmarshal的错误,您会清楚地看到这就是问题所在:

json: 无法将数组解组为类型为main.OHLC的Go结构体字段 Trades.result.XXBTZUSD

如果您想将JSON数组解组为Go结构体,您必须使Go结构体类型实现json.Unmarshaler接口。

func (o *OHLC) UnmarshalJSON(data []byte) error {
    // 首先将数组解组为原始JSON切片
	raw := []json.RawMessage{}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

    // 创建一个函数,将每个原始JSON元素解组为字段
	unmarshalFields := func(raw []json.RawMessage, fields ...interface{}) error {
		if len(raw) != len(fields) {
			return errors.New("json数组中的元素数量错误")
		}
		for i := range raw {
			if err := json.Unmarshal([]byte(raw[i]), fields[i]); err != nil {
				return err
			}
		}
		return nil
	}

    // 调用函数
	return unmarshalFields(
        raw,
		&o.Time,
		&o.Open,
		&o.High,
		&o.Low,
		&o.Close,
		&o.Vwa,
		&o.Volume,
		&o.Count,
	)
}

https://go.dev/play/p/fkFKLkaNaSU

英文:

"Any ideas about what might be happening?"

The elements in the "XXBTZUSD" JSON array are arrays themselves, i.e. "XXBTZUSD" is an array of arrays. The OHLC type is a struct type. The stdlib will not, by itself, unmarshal a JSON array into a Go struct. Go structs can be used to unmarshal JSON objects. JSON arrays can be unmarshaled into Go slices or arrays.

You would clearly see that that's the issue if you would just print the error from json.Unmarshal:

> json: cannot unmarshal array into Go struct field
> Trades.result.XXBTZUSD of type main.OHLC

https://go.dev/play/p/D4tjXZVzDI_w


If you want to unmarshal a JSON array into a Go struct you have to have the Go struct type implement a the json.Unmarshaler interface.

func (o *OHLC) UnmarshalJSON(data []byte) error {
    // first unmarshal the array into a slice of raw json
	raw := []json.RawMessage{}
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

    // create a function that unmarshals each raw json element into a field
	unmarshalFields := func(raw []json.RawMessage, fields ...interface{}) error {
		if len(raw) != len(fields) {
			return errors.New("bad number of elements in json array")
		}
		for i := range raw {
			if err := json.Unmarshal([]byte(raw[i]), fields[i]); err != nil {
				return err
			}
		}
		return nil
	}

    // call the function
	return unmarshalFields(
        raw,
		&o.Time,
		&o.Open,
		&o.High,
		&o.Low,
		&o.Close,
		&o.Vwa,
		&o.Volume,
		&o.Count,
	)
}

https://go.dev/play/p/fkFKLkaNaSU

答案2

得分: 1

你的代码有一些问题:

  1. 删除行尾的分号,这是多余的。
  2. fmt.Errorf 返回错误,而不是打印它,每次都要检查错误并传播它。
  3. 我们可以在 Golang 中将数字和字符串数组转换为结构体。

为了实现你想要的输出,我们需要先转换为中间容器,然后再转换为我们想要的输出:

package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"

	"encoding/json"
	"io/ioutil"
)

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

type IntermediateResp struct {
	Error  []string           `json:"error"`
	Result IntermediateTrades `json:"result"`
}

type IntermediateTrades struct {
	Pair [][]interface{} `json:"XXBTZUSD"`
	Last int             `json:"last"`
}

type Trades struct {
	Pair []OHLC `json:"result"`
	Last int    `json:"last"`
}

type OHLC struct {
	TT     float64
	Open   string
	High   string
	Low    string
	Close  string
	Vwap   string
	Volume string
	Count  float64
}

/*func main() {
    var data = [...]Trade{
        Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
        Trade{5, "op", "hi", "lo", "cl", "vw", "vo", 2},
    }
}*/

func main() {
	fmt.Println("in main")
	err := getOhlc()
	if err != nil {
		log.Fatal(err)
	}
}

func buildOHLC(l []interface{}) (*OHLC, error) {
	if len(l) < 8 {
		return nil, errors.New("short list")
	}
	return &OHLC{
		TT:     l[0].(float64),
		Open:   l[1].(string),
		High:   l[2].(string),
		Low:    l[3].(string),
		Close:  l[4].(string),
		Vwap:   l[5].(string),
		Volume: l[6].(string),
		Count:  l[7].(float64),
	}, nil
}

func convert(r IntermediateResp) (*Resp, error) {
	result := &Resp{Error: r.Error, Result: Trades{Pair: make([]OHLC, len(r.Result.Pair)), Last: r.Result.Last}}
	for i, v := range r.Result.Pair {
		ohlc, err := buildOHLC(v)
		if err != nil {
			return nil, err
		}
		result.Result.Pair[i] = *ohlc
	}
	return result, nil
}

func getOhlc() error {
	fmt.Println("in ohlc func")
	resp, err := http.Get("https://api.kraken.com/0/public/OHLC?pair=XXBTZUSD")
	if err != nil {
		return fmt.Errorf("error after request, %v", err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	fmt.Println(string(body))
	if err != nil {

		return fmt.Errorf("error when reading %v", err)
	}

	var jsonData IntermediateResp
	err = json.Unmarshal(body, &jsonData)
	if err != nil {

		return fmt.Errorf("error when unmarshalling %v", err)
	}

	if len(jsonData.Error) > 0 {
		return fmt.Errorf("error")
	}

	convertedOhlc, err := convert(jsonData)
	if err != nil {
		return fmt.Errorf("error when convertedOhlc %v", err)
	}
	fmt.Println(convertedOhlc)
	return nil
}

我们定义了 IntermediateResp 和 IntermediateTrades 用于解析 JSON,然后将其转换为实际的 Resp。

我认为另一种方法是为 Trades 结构体使用自定义的 Unmarshal。

英文:

Your code had some issues:

  1. Remove semicolons from end of lines, it's redundant.
  2. fmt.Errorf return error, and not print it, every time check your error and propagate it.
  3. We can convert array of numbers and string to struct in golang.

for achieving your desired output we need to first convert to intermediate container and then convert to our wanted output:

package main

import (
	&quot;errors&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net/http&quot;

	//&quot;strings&quot;
	&quot;encoding/json&quot;
	&quot;io/ioutil&quot;
)

type Resp struct {
	Error  []string `json:&quot;error&quot;`
	Result Trades   `json:&quot;result&quot;`
}

type IntermediateResp struct {
	Error  []string           `json:&quot;error&quot;`
	Result IntermediateTrades `json:&quot;result&quot;`
}

type IntermediateTrades struct {
	Pair [][]interface{} `json:&quot;XXBTZUSD&quot;`
	Last int             `json:&quot;last&quot;`
}

type Trades struct {
	Pair []OHLC `json:&quot;result&quot;`
	Last int    `json:&quot;last&quot;`
}

type OHLC struct {
	TT     float64
	Open   string
	High   string
	Low    string
	Close  string
	Vwap   string
	Volume string
	Count  float64
}

/*func main() {
    var data = [...]Trade{
        Trade{5, &quot;op&quot;, &quot;hi&quot;, &quot;lo&quot;, &quot;cl&quot;, &quot;vw&quot;, &quot;vo&quot;, 2},
        Trade{5, &quot;op&quot;, &quot;hi&quot;, &quot;lo&quot;, &quot;cl&quot;, &quot;vw&quot;, &quot;vo&quot;, 2},
    }
}*/

func main() {
	fmt.Println(&quot;in main&quot;)
	err := getOhlc()
	if err != nil {
		log.Fatal(err)
	}
}

func buildOHLC(l []interface{}) (*OHLC, error) {
	if len(l) &lt; 8 {
		return nil, errors.New(&quot;short list&quot;)
	}
	return &amp;OHLC{
		TT:     l[0].(float64),
		Open:   l[1].(string),
		High:   l[2].(string),
		Low:    l[3].(string),
		Close:  l[4].(string),
		Vwap:   l[5].(string),
		Volume: l[6].(string),
		Count:  l[7].(float64),
	}, nil
}

func convert(r IntermediateResp) (*Resp, error) {
	result := &amp;Resp{Error: r.Error, Result: Trades{Pair: make([]OHLC, len(r.Result.Pair)), Last: r.Result.Last}}
	for i, v := range r.Result.Pair {
		ohlc, err := buildOHLC(v)
		if err != nil {
			return nil, err
		}
		result.Result.Pair[i] = *ohlc
	}
	return result, nil
}

func getOhlc() error {
	fmt.Println(&quot;in ohlc func&quot;)
	resp, err := http.Get(&quot;https://api.kraken.com/0/public/OHLC?pair=XXBTZUSD&quot;)
	if err != nil {
		return fmt.Errorf(&quot;error after request, %v&quot;, err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	fmt.Println(string(body))
	if err != nil {

		return fmt.Errorf(&quot;error when reading %v&quot;, err)
	}

	var jsonData IntermediateResp
	err = json.Unmarshal(body, &amp;jsonData)
	if err != nil {

		return fmt.Errorf(&quot;error when unmarshalling %v&quot;, err)
	}

	if len(jsonData.Error) &gt; 0 {
		return fmt.Errorf(&quot;error&quot;)
	}

	convertedOhlc, err := convert(jsonData)
	if err != nil {
		return fmt.Errorf(&quot;error when convertedOhlc %v&quot;, err)
	}
	fmt.Println(convertedOhlc)
	return nil
}

We define IntermediateResp and IntermediateTrades for Unmarshaling json and then convert it to actual Resp.

I think aother way is using custom Unmarshal for Trades struct.

huangapple
  • 本文由 发表于 2022年3月16日 14:13:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/71492496.html
匿名

发表评论

匿名网友

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

确定