Different names of JSON property during serialization and deserialization in golang

Viewed 553

Is it possible: to have one field in the struct, but different names for it during serialization/deserialization in Golang?

For example, I have the struct "Coordinates".

type Coordinates struct {
  red int
}

For deserialization from JSON want to have a format like this:

{
  "red":12
}

But when I will serialize the struct, the result should be like this one:

{
  "r":12
}
3 Answers

The standard lib does not support this out of the box, but using custom marshaler / unmarsaler you can do whatever you want to.

For example:

type Coordinates struct {
    Red int `json:"red"`
}

func (c Coordinates) MarshalJSON() ([]byte, error) {
    type out struct {
        R int `json:"r"`
    }

    return json.Marshal(out{R: c.Red})
}

(Note: struct fields must be exported to take part in the marshaling / unmarshaling process.)

Testing it:

s := `{"red":12}`
var c Coordinates
if err := json.Unmarshal([]byte(s), &c); err != nil {
    panic(err)
}

out, err := json.Marshal(c)
if err != nil {
    panic(err)
}

fmt.Println(string(out))

Output (try it on the Go Playground):

{"r":12}

There are several options:

  1. Use map
  2. Use two structs and cast:
type Coordinates1 struct {
   Red int `json:"red"`
}

type Coordinates2 struct {
   Red int `json:"r"`
}

// Cast from first to second:
var x Coordinates1
json.Unmarshal(data,&x)
y:=*(*Coordinates2)(&x)
json.Marshal(y)

@icza's answer using pass-by-value makes sense given your struct is small. For a larger struct you might want to use a pointer as your receiver for MarshalJSON.

Here is an alternative version using pointers and the new ability to cast between struct types with same fields but different struct tags. This does no copying, just casts the pointer type. (remember to also pass a pointer to json.Marshal - json.Marshal(&c) instead of json.Marshal(c))

type Coordinates struct {
    Red int `json:"red"`
}

type coordinatesOut struct {
    Red int `json:"r"`
}

func (c *Coordinates) MarshalJSON() ([]byte, error) {
    return json.Marshal((*coordinatesOut)(c))
}

Playground link

Another option would be to do the conversion on input (unmarshalling). I don't think it matters which way you do it, but one style may make more sense or be more readable for your code

type Coordinates struct {
    Red int `json:"r"`
}

type coordinatesIn struct {
    Red int `json:"red"`
}

func (c *Coordinates) UnmarshalJSON(data []byte) error {
    return json.Unmarshal(data, (*coordinatesIn)(c))
}

Playground link

Related