为什么我从JSON解码器中得到一个空的结构体?

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

Why am I getting an empty struct from JSON Decoder?

问题

我正在尝试调用AlphaVantage的Income Statement端点,并将响应解析为我的IncomeStmt结构体。它没有抛出任何错误,但是当我尝试打印结果时,incomeStmt的值是&{ [] []}

这是端点:https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol=IBM&apikey=demo

这是API响应结构:https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol=IBM&apikey=demo

到目前为止,我已经检查了我的IncomeStmtQuarterlyReportsAnnualReports结构体,所有字段的名称和类型(根据上面的API结构,都是字符串)都匹配。我检查了我是否正确导出了所有字段,并且我相信我的getJson函数工作正常,因为我已经能够使用该函数解析一个更简单的API响应。所以,我认为问题出在我如何创建要解析的结构体上。

我期望的结果是在GetIncomeStmt中打印出API响应:fmt.Printf("Unmarshaled IncomeStmt: %v \n", incomeStmt)

以下是代码:

type AnnualReports struct {
	FiscalDateEnding                  string `json:"fiscalDateEnding"`
	ReportedCurrency                  string `json:"reportedCurrency"`
	GrossProfit                       string `json:"grossProfit"`
	TotalRevenue                      string `json:"totalRevenue"`
	CostOfRevenue                     string `json:"costOfRevenue"`
	CostOfGoodsAndServicesSold        string `json:"costOfGoodsAndServicesSold"`
	OperatingIncome                   string `json:"operatingIncome"`
	SellingGeneralAndAdministrative   string `json:"sellingGeneralAndAdministrative"`
	ResearchAndDevelopment            string `json:"researchAndDevelopment"`
	OperatingExpenses                 string `json:"operatingExpenses"`
	InvestmentIncomeNet               string `json:"investmentIncomeNet"`
	NetInterestIncome                 string `json:"netInterestIncome"`
	InterestIncome                    string `json:"interestIncome"`
	InterestExpense                   string `json:"interestExpense"`
	NonInterestIncome                 string `json:"nonInterestIncome"`
	OtherNonOperatingIncome           string `json:"otherNonOperatingIncome"`
	Depreciation                      string `json:"depreciation"`
	DepreciationAndAmortization       string `json:"depreciationAndAmortization"`
	IncomeBeforeTax                   string `json:"incomeBeforeTax"`
	IncomeTaxExpense                  string `json:"incomeTaxExpense"`
	InterestAndDebtExpense            string `json:"interestAndDebtExpense"`
	NetIncomeFromContinuingOperations string `json:"netIncomeFromContinuingOperations"`
	ComprehensiveIncomeNetOfTax       string `json:"comprehensiveIncomeNetOfTax"`
	Ebit                              string `json:"ebit"`
	Ebitda                            string `json:"ebitda"`
	NetIncome                         string `json:"netIncome"`
}

type QuarterlyReports struct {
	FiscalDateEnding                  string `json:"fiscalDateEnding"`
	ReportedCurrency                  string `json:"reportedCurrency"`
	GrossProfit                       string `json:"grossProfit"`
	TotalRevenue                      string `json:"totalRevenue"`
	CostOfRevenue                     string `json:"costOfRevenue"`
	CostOfGoodsAndServicesSold        string `json:"costOfGoodsAndServicesSold"`
	OperatingIncome                   string `json:"operatingIncome"`
	SellingGeneralAndAdministrative   string `json:"sellingGeneralAndAdministrative"`
	ResearchAndDevelopment            string `json:"researchAndDevelopment"`
	OperatingExpenses                 string `json:"operatingExpenses"`
	InvestmentIncomeNet               string `json:"investmentIncomeNet"`
	NetInterestIncome                 string `json:"netInterestIncome"`
	InterestIncome                    string `json:"interestIncome"`
	InterestExpense                   string `json:"interestExpense"`
	NonInterestIncome                 string `json:"nonInterestIncome"`
	OtherNonOperatingIncome           string `json:"otherNonOperatingIncome"`
	Depreciation                      string `json:"depreciation"`
	DepreciationAndAmortization       string `json:"depreciationAndAmortization"`
	IncomeBeforeTax                   string `json:"incomeBeforeTax"`
	IncomeTaxExpense                  string `json:"incomeTaxExpense"`
	InterestAndDebtExpense            string `json:"interestAndDebtExpense"`
	NetIncomeFromContinuingOperations string `json:"netIncomeFromContinuingOperations"`
	ComprehensiveIncomeNetOfTax       string `json:"comprehensiveIncomeNetOfTax"`
	Ebit                              string `json:"ebit"`
	Ebitda                            string `json:"ebitda"`
	NetIncome                         string `json:"netIncome"`
}

type IncomeStmt struct {
	Symbol           string             `json:"symbol"`
	AnnualReports    []AnnualReports    `json:"annualReports"`
	QuarterlyReports []QuarterlyReports `json:"quarterlyReports"`
}

type Client interface {
	GetEarnings(symbol string) (*Earnings, error)
	GetIncomeStmt(symbol string) (*IncomeStmt, error)
}

type ClientOpts struct {
	ApiKey     string
	BaseUrl    string
	HttpClient *http.Client
}

type client struct {
	opts       ClientOpts
	httpClient *http.Client
}

func NewClient(opts ClientOpts) Client {
	if opts.ApiKey == "" {
		opts.ApiKey = secretProvider.GetEnv("ALPHA_VANTAGE_API_KEY")
	}
	if opts.BaseUrl == "" {
		opts.BaseUrl = secretProvider.GetEnv("ALPHA_VANTAGE_API_BASE_URL")
	}

	httpClient := opts.HttpClient

	if httpClient == nil {
		httpClient = &http.Client{Timeout: 10 * time.Second}
	}

	return &client{
		opts:       opts,
		httpClient: httpClient,
	}
}

func (c *client) get(u *url.URL) (*http.Response, error) {
	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.httpClient.Do(req)

	if err != nil {
		return nil, err
	}

	return resp, nil

}

// getJson将对http响应进行解析,如果解析失败则返回错误
func getJson(resp *http.Response, data interface{}) error {
	defer resp.Body.Close()
	return json.NewDecoder(resp.Body).Decode(&data)
}

func (c *client) GetIncomeStmt(symbol string) (*IncomeStmt, error) {
	u, err := url.Parse(fmt.Sprintf("%s?function=INCOME_STATEMENT&symbol=%s&apiKey=%s", c.opts.BaseUrl, symbol, c.opts.ApiKey))
	if err != nil {
		return nil, err
	}
	resp, err := c.get(u)

	if err != nil {
		return nil, err
	}

	incomeStmt := &IncomeStmt{}

	if err = getJson(resp, incomeStmt); err != nil {
		fmt.Print(err)
		log.Fatal("Error decoding data in getJson fn.")
		return nil, err
	}

	fmt.Printf("Unmarshaled IncomeStmt: %v \n", incomeStmt)

	return incomeStmt, nil
}

英文:

I'm trying to make an API call to AlphaVantage's Income Statement endpoint and then unmarshal the response into my IncomeStmt struct. It's not throwing any kind of error but when I try to print the result, I'm getting &{ [] []} as the value of incomeStmt.

This is the endpoint: https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol=IBM&apikey=demo

Here is the API response structure: https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol=IBM&apikey=demo

So far, I've combed through my IncomeStmt, QuarterlyReports and AnnualReports structs and all the fields match the name and types (all strings according to API structure above) from the API. I checked that I'm exporting all the fields correctly and I believe that my getJson function is working properly as I've been able to unmarshal a simpler API response using that function. So, I believe the issue is something with how I've created the structs to unmarshal into.

My expected result is to see the API response printed out in fmt.Printf("Unmarshaled IncomeStmt: %v \n", incomeStmt) from GetIncomeStmt.

Code Below:

type AnnualReports struct {
FiscalDateEnding                  string `json:"fiscalDateEnding"`
ReportedCurrency                  string `json:"reportedCurrency"`
GrossProfit                       string `json:"grossProfit"`
TotalRevenue                      string `json:"totalRevenue"`
CostOfRevenue                     string `json:"costOfRevenue"`
CostOfGoodsAndServicesSold        string `json:"costOfGoodsAndServicesSold"`
OperatingIncome                   string `json:"operatingIncome"`
SellingGeneralAndAdministrative   string `json:"sellingGeneralAndAdministrative"`
ResearchAndDevelopment            string `json:"researchAndDevelopment"`
OperatingExpenses                 string `json:"operatingExpenses"`
InvestmentIncomeNet               string `json:"investmentIncomeNet"`
NetInterestIncome                 string `json:"netInterestIncome"`
InterestIncome                    string `json:"interestIncome"`
InterestExpense                   string `json:"interestExpense"`
NonInterestIncome                 string `json:"nonInterestIncome"`
OtherNonOperatingIncome           string `json:"otherNonOperatingIncome"`
Depreciation                      string `json:"depreciation"`
DepreciationAndAmortization       string `json:"depreciationAndAmortization"`
IncomeBeforeTax                   string `json:"incomeBeforeTax"`
IncomeTaxExpense                  string `json:"incomeTaxExpense"`
InterestAndDebtExpense            string `json:"interestAndDebtExpense"`
NetIncomeFromContinuingOperations string `json:"netIncomeFromContinuingOperations"`
ComprehensiveIncomeNetOfTax       string `json:"comprehensiveIncomeNetOfTax"`
Ebit                              string `json:"ebit"`
Ebitda                            string `json:"ebitda"`
NetIncome                         string `json:"netIncome"`
}
type QuarterlyReports struct {
FiscalDateEnding                  string `json:"fiscalDateEnding"`
ReportedCurrency                  string `json:"reportedCurrency"`
GrossProfit                       string `json:"grossProfit"`
TotalRevenue                      string `json:"totalRevenue"`
CostOfRevenue                     string `json:"costOfRevenue"`
CostOfGoodsAndServicesSold        string `json:"costOfGoodsAndServicesSold"`
OperatingIncome                   string `json:"operatingIncome"`
SellingGeneralAndAdministrative   string `json:"sellingGeneralAndAdministrative"`
ResearchAndDevelopment            string `json:"researchAndDevelopment"`
OperatingExpenses                 string `json:"operatingExpenses"`
InvestmentIncomeNet               string `json:"investmentIncomeNet"`
NetInterestIncome                 string `json:"netInterestIncome"`
InterestIncome                    string `json:"interestIncome"`
InterestExpense                   string `json:"interestExpense"`
NonInterestIncome                 string `json:"nonInterestIncome"`
OtherNonOperatingIncome           string `json:"otherNonOperatingIncome"`
Depreciation                      string `json:"depreciation"`
DepreciationAndAmortization       string `json:"depreciationAndAmortization"`
IncomeBeforeTax                   string `json:"incomeBeforeTax"`
IncomeTaxExpense                  string `json:"incomeTaxExpense"`
InterestAndDebtExpense            string `json:"interestAndDebtExpense"`
NetIncomeFromContinuingOperations string `json:"netIncomeFromContinuingOperations"`
ComprehensiveIncomeNetOfTax       string `json:"comprehensiveIncomeNetOfTax"`
Ebit                              string `json:"ebit"`
Ebitda                            string `json:"ebitda"`
NetIncome                         string `json:"netIncome"`
}
type IncomeStmt struct {
Symbol           string             `json:"symbol"`
AnnualReports    []AnnualReports    `json:"annualReports"`
QuarterlyReports []QuarterlyReports `json:"quarterlyReports"`
}
type Client interface {
GetEarnings(symbol string) (*Earnings, error)
GetIncomeStmt(symbol string) (*IncomeStmt, error)
}
type ClientOpts struct {
ApiKey     string
BaseUrl    string
HttpClient *http.Client
}
type client struct {
opts       ClientOpts
httpClient *http.Client
}
func NewClient(opts ClientOpts) Client {
if opts.ApiKey == "" {
opts.ApiKey = secretProvider.GetEnv("ALPHA_VANTAGE_API_KEY")
}
if opts.BaseUrl == "" {
opts.BaseUrl = secretProvider.GetEnv("ALPHA_VANTAGE_API_BASE_URL")
}
httpClient := opts.HttpClient
if httpClient == nil {
httpClient = &http.Client{Timeout: 10 * time.Second}
}
return &client{
opts:       opts,
httpClient: httpClient,
}
}
func (c *client) get(u *url.URL) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
// getJson will take the http response and unmarshal it, returning an error if unsuccessful
func getJson(resp *http.Response, data interface{}) error {
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(&data)
}
func (c *client) GetIncomeStmt(symbol string) (*IncomeStmt, error) {
u, err := url.Parse(fmt.Sprintf("%s?function=INCOME_STATEMENT&symbol=%s&apiKey=%s", c.opts.BaseUrl, symbol, c.opts.ApiKey))
if err != nil {
return nil, err
}
resp, err := c.get(u)
if err != nil {
return nil, err
}
incomeStmt := &IncomeStmt{}
if err = getJson(resp, incomeStmt); err != nil {
fmt.Print(err)
log.Fatal("Error decoding data in getJson fn.")
return nil, err
}
fmt.Printf("Unmarshaled IncomeStmt: %v \n", incomeStmt)
return incomeStmt, nil
}

答案1

得分: 1

问题在于我错误地将apiKey以驼峰命名法写入了端点URL中。这样做导致我的API密钥未在GET请求中传递给AlphaVantage。我是通过Cerise上面的评论发现了这个问题的。尽管API返回了200状态(即使请求格式不正确!),但它促使我尝试用ioutil.ReadAll()以不同的方式读取响应体。需要注意的是,根据这里的帖子,ReadAll()方法在实际使用中并不高效。我只是出于调试目的使用了以下代码:

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("Error using readall")
	}
	fmt.Println(string(body))

这段代码打印出一直被隐藏的错误信息:

{
    "Error Message": "the parameter apikey is invalid or missing. Please claim your free API key on (https://www.alphavantage.co/support/#api-key). It should take less than 20 seconds."
}

将查询参数apiKey改为小写的apikey后,我得到了预期的响应。故事的寓意是,我需要添加额外的错误处理,以防API未返回预期的响应体,这样程序就不会悄无声息地失败。

英文:

The issue here was that I incorrectly camelCased apiKey in the endpoint URL. Doing so caused my API key not to be passed in the GET request to AlphaVantage. I found this thanks to Cerise's comment above. While the API was returning a 200 status (even with my malformed request!), it prompted me to try to read the response body in a different way with ioutil.ReadAll(). It's important to note that the ReadAll() method is not performant for actual use according to this post here. I used the following only for debugging purposes:

	body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal("Error using readall")
}
fmt.Println(string(body))

which printed the error message that was being hidden all along:

{
"Error Message": "the parameter apikey is invalid or missing. Please claim your free API key on (https://www.alphavantage.co/support/#api-key). It should take less than 20 seconds."
}

Lowercasing the query parameter apiKey to apikey caused me to get the expected response. Moral of the story is I need to add additional error handling in case the API is not returning the expected response body so that the program doesn't fail silently.

huangapple
  • 本文由 发表于 2022年12月29日 02:17:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/74943678.html
匿名

发表评论

匿名网友

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

确定