Unmarshalling JSON string into a constant

The encoding/json Unmarshaler interface will work with the Operator type, but it must have a pointer as its receiver:

func (o *Operator) UnmarshalJSON(b []byte) error {
    str := strings.Trim(string(b), `"`)

    switch {
        case str == "CONTAINS":
            *o = CONTAINS

        default:
            *o = UNKNOWN
            // or return an error...
    }

    return nil
}

The JSON decoder will take the address of the Operator field from the Filter struct and invoke the UnmarshalJSON method on it.

Note that you could implement the more generic encoding/TextUnmarshaler instead, by changing UnmarshalJSON to UnmarshalText above.

Here is a playground example: http://play.golang.org/p/szcnC6L86u

Arguably it might be simpler to use a string base type for Operator instead: http://play.golang.org/p/FCCg1NOeYw


You can wrap your object in an Unmarshaler. ex:

package main

import (
        "encoding/json"
        "fmt"
)

type Operator int

const (
        UNKNOWN Operator = iota
        EQUALS
        CONTAINS
        BETWEEN
        DISTANCE
)

type Filter struct {
        Field       string   `json:"field"`
        RawOperator string   `json:"operator"`
        Operator    Operator `json:"-"`
        Values      []string `json:"values"`
}

type FilterUnmarshaler struct {
        Filter
}

func (f *FilterUnmarshaler) UnmarshalJSON(data []byte) error {
        if err := json.Unmarshal(data, &f.Filter); err != nil {
                return err
        }
        switch f.RawOperator {
        case "UNKOWN":
                f.Operator = UNKNOWN
        case "EQUALS":
                f.Operator = EQUALS
        case "CONTAINS":
                f.Operator = CONTAINS
        case "BETWEEN":
                f.Operator = BETWEEN
        case "DISTANCE":
                f.Operator = DISTANCE
        default:
                return fmt.Errorf("Unkown operator %s", f.RawOperator)
        }
        return nil
}

func main() {
        rawJson := []byte(`
{
    "operator": "BETWEEN",
    "field": "name",
    "values": [ "John", "Doe" ]
}
`)
        val := &FilterUnmarshaler{}
        if err := json.Unmarshal(rawJson, val); err != nil {
                panic(err)
        }
        fmt.Printf("%#v\n", val)
}

Without using a wrapper, I didn't find how not to fall in an infinite recursion.

This example shows how to unmarshal but you most likely want to apply it to Marshal as well in order to have a correct json with Operator strings from your object with integer operator.

You might also want to create a map instead of const. Or both, create a map and manually populate it with your const and their string equivalent.