Question

In my Go code I have the following situation:

type Operator int
const (
    UNKNOWN  Operator = iota
    EQUALS
    CONTAINS
    BETWEEN
    DISTANCE
)

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

My expected JSON would look like the following:

{
    "operator": "EQUALS",
    "field": "name",
    "values": [ "John", "Doe" ]
}

Can I create a mapping such that json.Unmarshal will set the right operator constant in the Filter struct? I am aware of the Unmarshaler interface but I don't think this can really be used on a constant value..

I would really like to keep the constants in my go code since this nicely enforces type checking and consistency when I pass them around.

Was it helpful?

Solution

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

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top