2 years ago

#39487

test-img

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

Accepted video resources