11

I have a struct that I'd like to Marshal into JSON differently depending on the context.

For example, sometimes I want to marshal like this:

    type MyStruct struct {
        Nickname       string `json:"nickname"`
        EmailAddress   string `json:"email_address"`
        PhoneNumber    string `json:"-"`
        MailingAddress string `json:"-"`
    }

And sometimes I want to marshal like this:

    type MyStruct struct {
        Nickname       string `json:"nickname"`
        EmailAddress   string `json:"email_address"`
        PhoneNumber    string `json:"phone_number"`
        MailingAddress string `json:"mailing_address"`
    }

Is there a simple way to do this without:

  1. Making 2 separate structs.
  2. Writing a custom marshaller.
  3. Temporarily removing the string values for PhoneNumber and MailingAddress (with an omitempty on the tag), marshaling and then adding them back.

If only there was a way to:

  1. Specify 2 sets of tags and tell the marshaler which ones to use.
  2. Dynamically change the tags at runtime.

2 Answers 2

17

I know you explicitly mention "without writing a custom marshaler", but in case someone sees this and thinks it should be avoided because of complexity, a custom marshaler to do what you want to do is really simple:

type MyStruct struct {
    Nickname       string `json:"nickname"`
    EmailAddress   string `json:"email_address"`
    PhoneNumber    string `json:"phone_number"`
    MailingAddress string `json:"mailing_address"`
    all            bool
}

func (ms MyStruct) MarshalJSON() ([]byte, error) {
    m := map[string]interface{}{} // ideally use make with the right capacity
    m["nickname"] = ms.Nickname
    m["email_address"] = ms.EmailAddress
    if ms.all {
        m["phone_number"] = ms.PhoneNumber
        m["mailing_address"] = ms.MailingAddress
    }
    return json.Marshal(m)
}

If the all field should be set by an external package, then a method could be defined on the struct, or the field could be made public (wouldn't affect the JSON since it is encoded via the custom marshaler).

Runnable example on the playground: http://play.golang.org/p/1N_iBzvuW4

Sign up to request clarification or add additional context in comments.

3 Comments

This was much simpler than I imagined. I ended up using a combination of this approach and the one described here: attilaolah.eu/2014/09/10/json-and-struct-composition-in-go
Here are some links for reference: The Marshaler type golang.org/pkg/encoding/json/#Marshaler. Blog article: blog.golang.org/json-and-go
The same principle would apply to unmarshalling - golang.org/pkg/encoding/json/#Unmarshaler
0

You can use reflection, not really the most efficient solution but it is convenient.

func MarshalSubset(obj interface{}, fields ...string) ([]byte, error) {
    if len(fields) == 0 {
        return json.Marshal(obj)
    }
    out := make(map[string]interface{}, len(fields))
    val := reflect.ValueOf(obj)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    if val.Kind() != reflect.Struct {
        panic("not a struct")
    }
    typ := val.Type()
    for _, f := range fields {
        val := val.FieldByName(f).Interface()
        rfld, _ := typ.FieldByName(f)
        tag := strings.Split(rfld.Tag.Get("json"), ",")
        if len(tag) > 0 {
            f = tag[0]
        }
        out[f] = val
    }

    return json.Marshal(out)
}

playground

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.