This article talks about decoding/unmarshalling JSON data that may have different types for the same field. For instance, if your application is accepting data from multiple sources and a field may arrive as either a int or as a string. How do you unmarshal a field that could be either an int or a string?   How do you unmarshal an enum that arrives a string? The short answer is that we unmarshal to an intermediary type using interface{}.  

Let's start with the final type that would be used by our application:

type TradeSide int32

const (
	Ask TradeSide = iota
	Bid
)

// Trade is our final type that is used by the application.
// Instead of directly converting from JSON we will use a
// custom unmarshaller since our data may arrive in an slightly
// different formats from different sources.
type Trade struct {
	Exchange string
	Base     string
	Quote    string
	TradeID  string
	Unix     uint64
	Side     TradeSide
	Price    float64
	Amount   float64
}

This type specifies that TradeId should be a string, it uses an enum for Side and uses float64 for Price and Amount.

You may also notice that the there are no struct tags for JSON. This is because we will define an anonymous struct that does use struct tags to perform the JSON deserialization.  This looks like:

raw := struct {
	Exchange string      `json:"exchange"`
	Base     string      `json:"base"`
	Quote    string      `json:"quote"`
	TradeID  interface{} `json:"tradeId"`
	Unix     uint64      `json:"unix"`
	Side     string      `json:"side"`
	Price    interface{} `json:"price"`
	Amount   interface{} `json:"amount"`
}{}

For fields that could be various types we can use interface{} to put whatever data happens to unmarshal into it. This gets used in json.Unmarshal :

err := json.Unmarshal(bytes, &raw)
if err != nil {
	// handle err
}

Now that we have data stored in our raw, we need to decode the values into our final struct. We do this by switching on the type of the value store in raw.TradeId, raw.Side, raw.Price, or raw.Amount.

For instance we can check TradeId:

switch v := raw.TradeID.(type) {
case float64:
	trade.TradeID = strconv.Itoa(int(v))
case string:
	trade.TradeID = v
}

In Go, numeric types are always float64. We want our final TradeID value to be a string. So our code checks if it is a numeric type and converts it to a string. If it's already a string then we don't need to do anything else.

If we want to convert a string value Side into our enum values we can perform a similar action:

switch raw.Side {
case "ask", "sell":
	trade.Side = Ask
case "bid", "buy":
	trade.Side = Bid
}

Here the values could be either ask or sell and we use the enum value Ask.

That's pretty much all there is to it. Below is a fuly implemented version that processes all of our variable fields.

// UnmarshalTrade converts json bytes into a Trade instance and
// is flexible about how it handles json fields which may have
// different values
func UnmarshalTrade(bytes []byte) (*Trade, error) {
	// Construct an anonymous struct that has looser typing
	// than our output field. We use this as a temporarily
	// placeholder to parse the contents and construct
	// a properly constructed final result
	raw := struct {
		Exchange string      `json:"exchange"`
		Base     string      `json:"base"`
		Quote    string      `json:"quote"`
		TradeID  interface{} `json:"tradeId"`
		Unix     uint64      `json:"unix"`
		Side     string      `json:"side"`
		Price    interface{} `json:"price"`
		Amount   interface{} `json:"amount"`
	}{}
	err := json.Unmarshal(bytes, &raw)
	if err != nil {
		return nil, err
	}

	// Construct our Trade instance with as much information as
	// possible from the raw data
	trade := &Trade{
		Exchange: raw.Exchange,
		Base:     raw.Base,
		Quote:    raw.Quote,
		Unix:     raw.Unix,
	}

	// Populate TradeId by converting the value into a string
	// depending on the type of the value received
	switch v := raw.TradeID.(type) {
	case float64:
		trade.TradeID = strconv.Itoa(int(v))
	case string:
		trade.TradeID = v
	}

	// Populate the Side property, which will either base
	switch raw.Side {
	case "ask", "sell":
		trade.Side = Ask
	case "bid", "buy":
		trade.Side = Bid
	}

	// Populate Price by converting the value into a float
	// depending on the type received in JSON
	switch v := raw.Price.(type) {
	case float64:
		trade.Price = v
	case string:
		{
			p, err := strconv.ParseFloat(string(v), 64)
			if err != nil {
				return nil, err
			}
			trade.Price = p
		}
	}
	
	// Populate Amount by converting the value into a float
	// depending on the type received in JSON
	switch v := raw.Amount.(type) {
	case float64:
		trade.Amount = v
	case string:
		{
			p, err := strconv.ParseFloat(string(v), 64)
			if err != nil {
				return nil, err
			}
			trade.Amount = p
		}
	}

	return trade, nil
}

You can see a full version below or give it a shot in the playground: https://play.golang.org/p/dD3BnizCyxH

Decoding Arbitrary Data

Lastly, if we wanted to get super crazy instead of unmarshalling to an intermediary type, we could instead unmarshal to an interface{}:

var raw interface{}
err := json.Unmarshal(bytes, &raw)
if err != nil {
	// handle err
}

However, this requires us to access things via a map of type map[string]interface{}.  If you're curious about this check out the "Decoding Arbitrary Data" section of JSON and Go.