How can I skip a field for json.Marshal & not for json.Unmarshal in go?

8.6k views Asked by At
type Alpha struct { 
  Name            string `json:"name"`
  SkipWhenMarshal string `json:"skipWhenMarshal"`
}

func MarshalJSON(out interface{}){
  json.Marshal(out)
} 

Is it possible to ignore the SkipWhenMarshal field when I do json.Marshal but not when I do json.Unmarshal. It should work for any type who calls MarshalJSON

5

There are 5 answers

1
Volker On

What you want simply cannot be done with encoding/json.

But you can have two types

type AlphaIn struct { 
    Name string `json:"name"`
    Bar  string `json:"skipWhenMarshal"`
}

type AlphaOut struct { 
    Name string `json:"name"`
    Bar  string `json:"-"`
}

Use AlphaIn to deserialise JSON with encoding/json.Unmarshal and use AlphaOut to serialise a struct with encoding/json.Marshal to JSON.

Now this alone would be absolute painful to work with but: struct tags do not play a role in convertibility between types which lets you convert from AlphaIn to AlphaOut with a simple type conversion:

var a AlphaIn = ...
var b AlphaOut = AlphaOut(a)

(A saner naming scheme would be Alpha and AlphaToJSON or soemthing like this.)

1
majidarif On

I would write a custom marshal function like so: playground

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
    "strings"
)

type Alpha struct {
    Name            string `json:"name"`
    SkipWhenMarshal string `json:"SkipWhenMarshal,skip"`
}

func main() {
    var a Alpha
    a.Name = "John"
    a.SkipWhenMarshal = "Snow"

    out, _ := marshal(a)

    fmt.Println(string(out))

    var b Alpha
    json.Unmarshal([]byte(`{"Name":"Samwell","SkipWhenMarshal":"Tarly"}`), &b)

    fmt.Println(b)
}

// custom marshaling function for json that accepts an additional tag named skip to ignore the field
func marshal(v Alpha) ([]byte, error) {
    m := map[string]interface{}{}

    ut := reflect.TypeOf(v)
    uv := reflect.ValueOf(v)

    for i := 0; i < ut.NumField(); i++ {
        field := ut.Field(i)
        js, ok := field.Tag.Lookup("json")
        if !ok || !strings.Contains(js, "skip") {
            intf := uv.Field(i).Interface()
            switch val := intf.(type) {
            case int, int8, uint8:
                m[field.Name] = val
            case string:
                m[field.Name] = val
            }

        }
    }

    return json.Marshal(m)
}

It basically rebuilds the struct as a map without the skipped fields and passes it to the original Marshal function.

Now just add the SKIP tag to any field and it should work.

You just need to improve this part:

switch val := intf.(type) {

Since this only assumes you only have strings or ints as fields. Handling more types would be more ideal.

10
Eli Bendersky On

Field tag modifiers like "omitempty" and "-" apply to both marshaling and unmarshaling, so there's no automatic way.

You can implement a MarshalJSON for your type that ignores whatever fields you need. There's no need to implement a custom unmarshaler, because the default works for you.

E.g. something like this:

type Alpha struct {
    Id              int32
    Name            string
    SkipWhenMarshal string
}

func (a Alpha) MarshalJSON() ([]byte, error) {
    m := map[string]string{
        "id":   fmt.Sprintf("%d", a.Id),
        "name": a.Name,
        // SkipWhenMarshal *not* marshaled here
    }

    return json.Marshal(m)
}

You can also make it simpler by using an alias type:

func (a Alpha) MarshalJSON() ([]byte, error) {
    type AA Alpha
    aa := AA(a)
    aa.SkipWhenMarshal = ""

    return json.Marshal(aa)
}

Here SkipWhenMarshal will be output, but its value is zeroed out. The advantage in this approach is that if Alpha has many fields, you don't have to repeat them.

0
Silent Assassin On

You can try this i.e. breaking the structure into components -> composition

while unmarshalling use Beta type. It will unmarshall only fields that are defined inside Beta struct

type Beta struct {
  Name            string `json:"name"`
}

type Alpha struct {
    *Beta
    SkipWhenMarshal string `json:"skipWhenMarshal"`
}
1
jchavanton On

If the structure member variable starts with a lower case, it will not be incuded.

Example:

type Alpha struct {
    Name            string `json:"name"`
    skipWhenMarshal string
}