2 years ago
#39487
7rhvnn
Unmarshal Nested JSON Object With Go
I'm making requests to an API that returns a JSON structure that is difficult to parse, especially in Go. Here is the gist of the data in tabular form:
Ticker | Jan 3 | Jan 4 | Jan 5 | Jan 6 |
---|---|---|---|---|
AAPL | 182.01 | 179.70 | 174.92 | 172 |
NFLX | 597.37 | 591.15 | 567.52 | 553.29 |
However, the requested data is completely unordered and unnecessarily nested:
{
"records": [
{
"id": "NFLX",
"data": {
"date": "2022-01-04",
"price": 591.15
}
},
{
"id": "AAPL",
"data": {
"date": "2022-01-03",
"price": 182.01
}
},
{
"id": "AAPL",
"data": {
"date": "2022-01-06",
"price": 172
}
},
{
"id": "NFLX",
"data": {
"date": "2022-01-05",
"price": 567.52
}
},
{
"id": "AAPL",
"data": {
"date": "2022-01-05",
"price": 174.92
}
},
{
"id": "NFLX",
"data": {
"date": "2022-01-06",
"price": 553.29
}
},
{
"id": "NFLX",
"data": {
"date": "2022-01-03",
"price": 597.37
}
},
{
"id": "AAPL",
"data": {
"date": "2022-01-04",
"price": 179.7
}
}
]
}
My goal is to create a map using Ticker as the key and a numpy-like array of prices - ordered by descending date - as the value, such as:
[{Ticker:NFLX Prices:[553.29 567.52 591.15 597.37]} {Ticker:AAPL Prices:[172 174.92 179.7 182.01]}]
To accomplish this, I'm creating several intermediate structs, performing small operations on each along the way to get it into this final format. I can't help but to think there is a better solution. But for now, here is my current implementation:
func NewRecord(id string, prices []float64) Record {
return Record{
Ticker: id,
Prices: prices,
}
}
type Record struct {
Ticker string
Prices []float64
}
type Records []Record
func (rs *Records) UnmarshalJSON(bs []byte) error {
type entry struct {
Date string `json:"date"`
Price float64 `json:"price"`
}
type payload struct {
EntrySlice []struct {
Ticker string `json:"id"`
Entry entry `json:"data"`
} `json:"records"`
}
var p payload
if err := json.Unmarshal(bs, &p); err != nil {
return err
}
// Create a DataFrame Like Map Using Ticker as the Key
dataframe := make(map[string][]entry)
for _, x := range p.EntrySlice {
dataframe[x.Ticker] = append(dataframe[x.Ticker], entry{
x.Entry.Date,
x.Entry.Price,
})
}
// Sort Entries (DataFrame Values) By Date Descending (i.e. Most Recent First)
for _, entry := range dataframe {
sort.Slice(entry, func(i, j int) bool { return entry[i].Date > entry[j].Date })
}
// Drop dates to create an array-like structure indexed by ticker
var records Records
for id, data := range dataframe {
var fx []float64
for _, entry := range data {
fx = append(fx, entry.Price)
}
records = append(records, NewRecord(id, fx))
}
*rs = records
return nil
}
This code works. But as someone relatively new to Go, I feel like I'm doing a lot more than what is necessary.
I'd like to learn what others are doing in these situations to find a method that is both idiomatic and parsimonious.
playground: https://play.golang.com/p/p3Ft0Ts4oSN
go
unmarshalling
0 Answers
Your Answer