Assuming I am correct about what you wanted then that would imply what you really wanted was for your output to be {"bar":"Bar"}
when you call json.MarshalWithESTag()
.
You cannot add a MarshalWithESTag()
method to the the json
package because Go does not allow for safe monkey patching. However, you can add a MarshalWithESTag()
method to your Foo
struct, and this example also shows you how to call it:
func (f Foo) MarshalWithESTag() ([]byte, error) {
data, err := json.Marshal(f)
return data,err
}
func main() {
f := &Foo{"Bar"}
data, _ := f.MarshalWithESTag()
log.Println(string(data)) // -> {"bar":"Bar"}
}
Next you need to add a MarshalJSON()
method to your Foo
struct. This will get called when you call json.Marshal()
and pass an instance of Foo
to it.
The following is a simple example that hard-codes a return value of {"hello":"goodbye"}
so you can see in the playground how adding a MarshalJSON()
to Foo
affects json.Marshal(Foo{"Bar"})
:
func (f Foo) MarshalJSON() ([]byte, error) {
return []byte(`{"hello":"goodbye"}`),nil
}
The output for this will be:
{"hello":"goodbye"}
Inside the MarshalJSON()
method we need to produce JSON with the es
tags instead of the json
tags meaning we will need to generate JSON within the method because Go does not provide us with the JSON; it expects us to generate it.
And the easiest way to generate JSON in Go is to use json.Marshal()
. However, if we use json.Marshal(f)
where f
is an instance of Foo
that gets passed as the receiver when calling MarshalJson()
it will end up in an infinite recursive loop!
The solution is to create a new struct type based on and identical to the existing type of Foo
, except for its identity. Creating a new type esFoo
based on Foo
is as easy as:
type esFoo Foo
Since we have esFoo
we can now cast our instance of Foo
to be of type esFoo
to break the association with our custom MarshalJSON()
. This works because our method was specific to the type with the identity of Foo
and not with the type esFoo
. Passing an instance of esFoo
to json.Marshal()
allows us to use the default JSON marshalling we get from Go.
To illustrate, here you can see an example that uses esFoo
and sets its Bar
property to "baz"
giving us output of {"test":"baz"}
(you can also see it run in the Go playground):
type esFoo Foo
func (f Foo) MarshalJSON() ([]byte, error) {
es := esFoo(f)
es.Bar = "baz"
_json,err := json.Marshal(es)
return _json,err
}
The output for this will be:
{"test":"baz"}
Next we process and manipulate the JSON inside MarshalJSON()
. This can be done by using json.Unmarshal()
to an interface{}
variable which we can then use a type assertion to treat the variable as a map.
Here is a standalone example unrelated to the prior examples that illustrates this by printing map[maker:Chevrolet model:Corvette year:2021]
(Again you can see it work in the Go Playground):
package main
import (
"encoding/json"
"fmt"
)
type Car struct {
Maker string `json:"maker" es:"fabricante"`
Model string `json:"model" es:"modelo"`
Year int `json:"year" es:"año"`
}
var car = Car{
Maker:"Chevrolet",
Model:"Corvette",
Year:2021,
}
func main() {
_json,_ := json.Marshal(car)
var intf interface{}
_ = json.Unmarshal(_json, &intf)
m := intf.(map[string]interface{})
fmt.Printf("%v",m)
}
The output for this will be:
map[maker:Chevrolet model:Corvette year:2021]
Our next challenge is to access the tags. Tags are accessible using Reflection. Go provides reflection functionality in the standard reflect package.
Using our Car
struct from above, here is a simple example that illustrates how to use Reflection. It uses the reflect.TypeOf()
function to retrieve the type as a value and then introspects that type to retrieve the tags for each field. The code for retrieving each tag is t.Field(i).Tag.Lookup("es")
, which is hopefully somewhat self-explanatory (and again, check it out in the Go Playground):
func main() {
t := reflect.TypeOf(car)
for i:=0; i<t.NumField();i++{
tag, _ := t.Field(i).Tag.Lookup("es")
fmt.Printf("%s\n",tag)
}
}
The output for this will be:
fabricante
modelo
año
Now that we have covered all the building blocks we can bring it all together into a working solution. The only addition worth mentioning are the creation of a new map variable _m
of the same length as m
to allow us to store the values using the es
tags:
func (f Foo) MarshalJSON() ([]byte, error) {
es := esFoo(f)
_json,err := json.Marshal(es)
{
if err != nil {
goto end
}
var intf interface{}
err = json.Unmarshal(_json, &intf)
if err != nil {
goto end
}
m := intf.(map[string]interface{})
_m := make(map[string]interface{},len(m))
t := reflect.TypeOf(f)
i := 0
for _,v := range m {
tag, found := t.Field(i).Tag.Lookup("es")
if !found {
continue
}
_m[tag] = v
i++
}
_json,err = json.Marshal(_m)
}
end:
return _json,err
}
However, there is still one detail left undone. With all the above code f.MarshalWithESTag()
will generate JSON for the es
tags, but so will json.Marshal(f)
and we want the latter to return its use of the json
tags.
So address that we just need to:
a. Add a local package variable useESTags
with an initial value of false
,
b. Modify f.MarshalWithESTag()
to set useESTags
to true
before calling json.Marshal()
, and then
c. To set useESTags
back to false
before returning, and
d. Lastly modify MarshalJSON()
to only perform the logic required for the es
tags if useESTags
is set to true
:
Which brings us to the final code — with a second property in Foo
to provide a better example (and finally, you can of course see here in the Go Playground):
package main
import (
"encoding/json"
"log"
"reflect"
)
type Foo struct {
Foo string `json:"test" es:"bar"`
Bar string `json:"live" es:"baz"`
}
type esFoo Foo
var useESTags = false
func (f Foo) MarshalWithESTag() ([]byte, error) {
useESTags = true
data, err := json.Marshal(f)
useESTags = false
return data,err
}
func (f Foo) MarshalJSON() ([]byte, error) {
es := esFoo(f)
_json,err := json.Marshal(es)
if useESTags {
if err != nil {
goto end
}
var intf interface{}
err = json.Unmarshal(_json, &intf)
if err != nil {
goto end
}
m := intf.(map[string]interface{})
_m := make(map[string]interface{},len(m))
t := reflect.TypeOf(f)
i := 0
for _,v := range m {
tag, found := t.Field(i).Tag.Lookup("es")
if !found {
continue
}
_m[tag] = v
i++
}
_json,err = json.Marshal(_m)
}
end:
return _json,err
}
func main() {
f := &Foo{"Hello","World"}
data, _ := json.Marshal(f)
log.Println(string(data)) // -> {"test":"Hello","live":"World"}
data, _ = f.MarshalWithESTag()
log.Println(string(data)) // -> {"bar":"Hello","baz":"World"}
}