Search code examples
jsongostructgo-interface

How to ignore MarshalJSON implementation of a struct (with nested structs)?


Is it possible to ignore a custom MarshalJSON implementation of a struct, and use just standard marshaling function instead?

The struct is complex, and has a lot of nested structs, all of which are using custom MarshalJSON, and I would like to ignore them all.

I feel that it should be trivial. Do you have an idea?

Some details

An obvious solution with a new type creation does not work well, because the nested structs still use their MarshalJSONs.

Here is an example of the code:

func (de DeploymentExtended) MarshalJSON() ([]byte, error) {
    objectMap := make(map[string]interface{})
    if de.Location != nil {
        objectMap["location"] = de.Location
    }
    if de.Properties != nil {
        objectMap["properties"] = de.Properties
    }
    if de.Tags != nil {
        objectMap["tags"] = de.Tags
    }
    return json.Marshal(objectMap)
}

(source: https://github.com/Azure/azure-sdk-for-go/blob/v62.0.0/services/resources/mgmt/2020-10-01/resources/models.go#L366)

And there are a lot of properties (like Name, etc), which I would like to see in my JSON (the same for Properties and other nested structs).

The Python implementation of this code provides that data, my software use it, and I (porting the code to Go) would like to be able to export these data from my Go program too.


Solution

  • You can do this two ways:

    • custom types (to hide the MarshalJSON methods); or
    • custom marshaler (using reflect to ignore any MarshalJSON methods at runtime)

    Custom Types

    For example, take these nested types:

    type Y struct {
        FieldZ string
    }
    type X struct {
        Name string
        Y    Y
    }
    
    
    func (x *X) MarshalJSON() ([]byte, error) { return []byte(`"DONT WANT THIS"`), nil }
    func (y *Y) MarshalJSON() ([]byte, error) { return []byte(`"DEFINITELY DONT WANT THIS"`), nil }
    

    one would need to shadow these types to avoid the unwanted MarshalJSON methods from being invoked:

    type shadowY struct {
        FieldZ string
    }
    type shadowX struct {
        Name string
        Y    shadowY
    }
    
    //
    // transform original 'x' to use our shadow types
    //
    x2 := shadowX{
        Name: x.Name,
        Y:    shadowY(x.Y),
    }
    

    https://go.dev/play/p/vzKtb0gZZov


    Reflection

    Here's a simple reflect-based JSON marshaler to achieve what you want. It assumes that all the custom marshalers use pointer receivers - and dereferences the pointer so the standard library's json.Marshal will not "see" them:

    func MyJSONMarshal(v interface{}) (bs []byte, err error) {
        k := reflect.TypeOf(v).Kind() // ptr or not?
    
        if k != reflect.Ptr {
            return json.Marshal(v)
        }
    
        // dereference pointer
        v2 := reflect.ValueOf(v).Elem().Interface()
        return MyJSONMarshal(v2)
    }
    

    YMMV with this method.

    https://go.dev/play/p/v9YjYRno7RV