Musings by @thedevel

JSON unmarshaling of primitive values

332 words · 01 Jan 2016

A common pattern in Go is using constants for enumerable sets of values. Although not required, it is recommended to define a custom type for holding the constant values.

type Size uint8

const (
    _ Size = iota
    Small
    Medium
    Large
)

One consideration when using integer-based constants is how they will be encoded outside of the program. For example, if the size is JSON encoded, it will be encoded as the number it corresponds to, Small is 1. To external programs this isn't useful since the context of the number is lost.

The standard technique for handling this is implementing the Marshaler and Unmarshaler interfaces on the custom type. If we are choosing to encode Size as a string then it makes sense to implement the Stringer interface as well.

// String returns the size encoded as a string.
func (s Size) String() string {
    switch s {
    case 1:
        return "small"
    case 2:
        return "medium"
    case 3:
        return "large"
    }
    return ""
}

// MarshalJSON encodes the type as a string.
func (s Size) MarshalJSON() ([]byte, error) {
    // Simply call the `String()` method to get the corresponding string.
    return json.Marshal(s.String())
}

// UnmarshalJSON decodes the JSON representation into the native size.
func (s *Size) UnmarshalJSON(b []byte) error {
    // Since the bytes are of a JSON string, simply decode it as a string first.
    // If there is a problem here, return the error.
    var x string
    if err := json.Unmarshal(b, &x); err != nil {
        return err
    }

    // Switch on the string and set the Size to the corresponding constant value.
    // Note that this method was defined as a pointer receiver so we can assign
    // the new value into that memory location. If this is confusing read this:
    // https://golang.org/doc/faq#methods_on_values_or_pointers
    switch x {
    case "small":
        *s = Small
    case "medium":
        *s = Medium
    case "large":
        *s = Large
    }

    return nil
}

Here is the working example on Go Playground: http://play.golang.org/p/yrlPeopuW9