2
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

4 Answers4

4

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.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
  • I need something that can be used in general. I am working with a lot of types that face this problem! –  May 18 '21 at 14:05
  • @NiranjanShetty: I'm not aware of a built-in options to do this. Adding some more details to the answer – Eli Bendersky May 18 '21 at 14:06
  • Why would you want to import something from json, but not translate it back to json? – Chen A. May 18 '21 at 14:10
  • do you mean something like func MarshalJSON(out interface{}, fieldsTobeIgnored ..interface{}){} can you improve the function if so? –  May 18 '21 at 14:10
  • @ChenA. I have an object field & and id field, object already has id so I don't wish to send it again! –  May 18 '21 at 14:11
  • So there is no other way than defining it like that for each type? I have 100s of types :( Would you say it's terrible to repeat the field value in JSON response? –  May 18 '21 at 14:28
  • @NiranjanShetty: no built-in way, but see my update with an approach that has less repetitive code – Eli Bendersky May 18 '21 at 14:34
  • @EliBendersky why do you have to define a special type AA, and without it, it doesn't work? – Chen A. May 18 '21 at 14:49
  • @ChenA.: infinite recursion with `Alpha.MarshalJSON` calling itself – Eli Bendersky May 18 '21 at 14:50
  • This is nice, but will be applicable if the field names are other than "SkipWhenMarhal"? –  May 19 '21 at 07:22
2

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.)

Volker
  • 40,468
  • 7
  • 81
  • 87
  • Yes, I am aware of this practice, wasn't sure we do this in go/go-gorm Besides, as you said it will be a pain to do this mid project with 300-500 structs –  May 19 '21 at 07:20
0

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.

majidarif
  • 18,694
  • 16
  • 88
  • 133
  • 1
    Unfortunately, as you're discovering with your edits, to make a really robust function you'll end up having to rewrite much of `json.Marshal` eventually :) – Eli Bendersky May 18 '21 at 15:42
0

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"`
}