1

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.

Igor Chubin
  • 61,765
  • 13
  • 122
  • 144
  • 2
    "I feel that it should be trivial" - it is not. There is no option to ignore a type's explicitly-defined JSON methods. You've found the only viable solution, using a new type, but indeed it will get quite messy with nested types. If this is something you're finding you need to do, it's likely that those types shouldn't have `MarshalJSON` defined on them to begin with. – Adrian Mar 02 '22 at 15:28
  • 1
    @Adrian: but it is really strange, in my opinion. Some structs have public data, and I can't really access it in some standard fashion. Basically, the only thing I need is to somehow dump them to some maps or whatever. I wonder why nobody wanted to it before... – Igor Chubin Mar 02 '22 at 16:04
  • 3
    Can you update the question with an example snippet of a top-level & nested type? It may be tedious, but working bottom up, it's not that difficult to redefine new types to prevent the original types' `MarshalJSON` logic. – colm.anseo Mar 02 '22 at 16:13
  • 2
    Your types implement `MarshalJSON()` because they implement how they should be marshaled into JSON. If this isn't what you want, then either the current implementations of `MarshalJSON()` should be changed, or you shouldn't use the values of these types to begin with. There are no multiple variants of JSON marshaling. If you need different logic, you need different types (with different logic implemented). – icza Mar 02 '22 at 16:15
  • @icza: It is how it is implemented in the azure API go module; there is also a python implementation (of the same module) that provides full JSON output. We have some code that uses this python lib, and we are porting our code to Go, but in Go the data is not available. – Igor Chubin Mar 02 '22 at 16:27
  • @colm.anseo: I added some infos above (not yet in the question; but anyway, you can understand my motivation better now) – Igor Chubin Mar 02 '22 at 16:28
  • 1
    @IgorChubin you could fork the dependency and change/drop the custom marshaler implementations. Or perhaps look for non-standard json marshalers that provide you with the features you are looking for. The standard library can't do what you want. – mkopriva Mar 02 '22 at 17:19

1 Answers1

2

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

colm.anseo
  • 19,337
  • 4
  • 43
  • 52
  • Yes, that is what I proposed originally, but I had a hope that there is a more elegant approach for that. Let's wait for some time, maybe someone will propose some better solution (or the haters will downvote the question into oblivion); if there is nothing better, I will take this one && thank you for the help – Igor Chubin Mar 02 '22 at 16:55
  • I added some links and infos, just to make it more concrete – Igor Chubin Mar 02 '22 at 17:01
  • Added a generic reflect-based solution. – colm.anseo Mar 02 '22 at 17:57