JSON Parsing with Go Embedded Structs
Tags: go, todayilearned
Published: 2020-06-11 21:48:42

Go has a feature called [embedding](https://golang.org/doc/effective_go.html#embedding) that allows you to create higher level interfaces by combining existing interfaces. But embedding can also be applied to structs, which is very useful for JSON parsing/decoding.

Consider the common case of decoding JSON objects that have a timestamp field, in this case encoded as a unix integer timestamp, as JSON has no native date support (this same solution could be applied to a iso-8601 encoded date string).

Here's a `PriceTick` json object and struct that will decode but isn't quite what we want. Ideally we want `Timestamp` to be a `time.Time` object rather than an `int64`.

```json
{
    "timestamp": 1591745820,
    "price": 321.655,
    "open": 321.68,
    "high": 321.7,
    "low": 321.61,
    "close": 321.61,
    "volume": 2608,
    "vwap": 321.66511
}
```
```go
type PriceTick struct {
	Timestamp int64     `json:"timestamp"`
	Price     float64   `json:"price"`
	Volume    float64   `json:"volume"`
	Open      float64   `json:"open"`
	High      float64   `json:"high"`
	Low       float64   `json:"low"`
	Close     float64   `json:"close"`
	VWAP      float64   `json:"vwap"`
}
```

Go lets you do custom json decoding on an object by defining the `UnmarshalJSON` interface method on a struct. But we don't want to do it for the entire `PriceTick` object, just the timestamp property.

We can define a custom `Timestamp` struct to hold the time object, but then you have to dig into the object whenever you want to access the `time.Time` object it's holding, which is not ideal if we want to replicate the native object structure we're reading from JSON.

This is where embedded structs shine.

We can modify the definition above to point to a struct that effectively wraps a `time.Time` object, and only do custom json decoding on that object.

```go
// Timestamp wraps a time object encoded as a int64 unix timestamp
// This can be used in structs and it automatically handles decoding
// int64 unix timestamps into a time.Time object.
type Timestamp struct {
	time.Time
}

// PriceTick is a tick returned from the TimeAndSales endpoint
type PriceTick struct {
	Timestamp Timestamp `json:"timestamp"`
	Time      string    `json:"time"`
	Price     float64   `json:"price"`
	Volume    float64   `json:"volume"`
	Open      float64   `json:"open"`
	High      float64   `json:"high"`
	Low       float64   `json:"low"`
	Close     float64   `json:"close"`
	VWAP      float64   `json:"vwap"`
}

// UnmarshalJSON decodes an int64 timestamp into a time.Time object
func (p *Timestamp) UnmarshalJSON(bytes []byte) error {
	// 1. Decode the bytes into an int64
	var raw int64
	err := json.Unmarshal(bytes, &raw)

	if err != nil {
		fmt.Printf("error decoding timestamp: %s\n", err)
		return err
	}

	// 2 - Parse the unix timestamp
	*&p.Time = time.Unix(raw, 0)
	return nil
}
```

By embedding the `time.Time` property in the `Timestamp` struct without a property name, we can call any `time.Time` methods directly on a Timestamp object.

From the Go documentation on embedding:
> When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.