Search code examples
jsongomarshallingunmarshalling

Golang Marshal/Unmarshal JSON with both exported and un-exported fields


I have seen plenty of ways to marshal/unmarshal structs that only have unexported fields. But how can I do this with mixed fields?

Given a struct:

type Test struct {
    fieldA string `json:"fieldA"`
    FieldB int    `json:"fieldB"`
    FieldC string `json:"fieldC"`
}

How can I write MarshalJSON/UnmarshalJSON functions so that fieldA is transported along with FieldB and FieldC?

The following compiles, but then overflows the call stack when I run it. My guess is I am recursively marshalling the object, but I am not sure of how else to preserve both exported and unexported fields when encoding.

func (t *Test) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
         *Test
         FieldA string `json:"fieldA"`
    }{
         t,
         t.fieldA,
    })
}

func (t *Test) UnmarshalJSON(b []byte) error {
    return json.Unmarshal(b, &t)
}

Is there a way to do this? Or should I re-think my data structures and maybe just export the field anyway?

Note: I'm aware I can do each field manually, but I'd like to avoid that if possible to make updating the code more manageable.


Solution

  • You can create a specific structure to handle the JSON serialization message: http://play.golang.org/p/d057T7qfVB

    type Test struct {
        fieldA string
        FieldB int
        FieldC string
    }
    
    type TestJSON struct {
        FieldA string `json:"fieldA"`
        FieldB int    `json:"fieldB"`
        FieldC string `json:"fieldC"`
    }
    
    func (t *Test) MarshalJSON() ([]byte, error) {
        return json.Marshal(TestJSON{
            t.fieldA,
            t.FieldB,
            t.FieldC,
        })
    }
    
    func (t *Test) UnmarshalJSON(b []byte) error {
        temp := &TestJSON{}
    
        if err := json.Unmarshal(b, &temp); err != nil {
            return err
        }
    
        t.fieldA = temp.FieldA
        t.FieldB = temp.FieldB
        t.FieldC = temp.FieldC
    
        return nil
    }