英文:
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
到目前为止,我已经检查了我的IncomeStmt
、QuarterlyReports
和AnnualReports
结构体,所有字段的名称和类型(根据上面的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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论