Reputation: 1648
I have a struct lets say
type Foo struct {
A string `json:",omitemtpy"
}
And I know that I can convert it to json easily with something like
json.Marshal(Foo{})
and it will respond with an empty json string.
But I need to return a json representation of the struct with all fields and "empty values" present within the json using the same struct. (in practice it is a very large struct so I can't just keep a copy without the tags)
What is the easiest way to do this?
Basically I need to create a json marshal of the struct that ignores the json omitempty tags.
This json creation does not need to be efficient or performant.
I would prefer if there is a library that exists for this type of task but most I have seen either create some special format or they respect omitempty
Edit:
Selected https://stackoverflow.com/a/77799949/2187510 as my answer with some additional work to allow for default values (using their code as reference)
defaultFoo := FoodWithPts{ Str: "helloWorld"}
dupFooType := dupType(reflect.TypeOf(defaultFoo))
foo := reflect.Zero(dupFooType).Interface()
// New additions
defaults, _ := json.Marshal(defaultFoo)
json.Unmarshal(defaults, &foo) // overwrites foo with defaults
// End New additions
data, err := json.Marshal(foo)
fmt.Println("dup FooWithPtrs:\n", string(data), err)
Output:
dup FooWithPtrs:
{"String":"helloWorld","Int":0,"Bar":null} <nil>
Upvotes: 3
Views: 599
Reputation: 417662
You can't modify tags at runtime, but you can create struct types at runtime using reflect.StructOf()
.
So the idea is to duplicate the struct type, but in the duplicate exclude the ,omitempty
option from JSON tags.
You can find all below examples on the Go Playground.
This is easier than one would think at first. We just have to do it recursively (a struct field may be another struct), and we definitely should handle pointers:
func dupType(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Pointer {
return reflect.PointerTo(dupType(t.Elem()))
}
if t.Kind() != reflect.Struct {
return t
}
var fields []reflect.StructField
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
sf.Type = dupType(sf.Type)
// Keep json tag but cut ,omitempty option if exists:
if tag, _ := strings.CutSuffix(sf.Tag.Get("json"), ",omitempty"); tag == "" {
sf.Tag = ""
} else {
sf.Tag = `json:"` + reflect.StructTag(tag) + `"`
}
fields = append(fields, sf)
}
return reflect.StructOf(fields)
}
Let's test it with this type:
type Foo struct {
Str string `json:"String,omitempty"`
Int int `json:",omitempty"`
Bar struct {
Float float64 `json:",omitempty"`
PtrInt int `json:",omitempty"`
Baz struct {
X int `json:"XXXX,omitempty"`
} `json:",omitempty"`
} `json:",omitempty"`
}
First this is what the JSON output would be without type duplication:
data, err := json.Marshal(Foo{})
fmt.Println("Foo:\n", string(data), err)
Output:
Foo:
{"Bar":{"Baz":{}}} <nil>
Note that we got Bar
and Baz
fields as those are structs.
Let's try with type duplication:
dupFooType := dupType(reflect.TypeOf(Foo{}))
foo := reflect.Zero(dupFooType).Interface()
data, err := json.Marshal(foo)
fmt.Println("dup Foo:\n", string(data), err)
This will output:
dup Foo:
{"String":"","Int":0,"Bar":{"Float":0,"PtrInt":0,"Baz":{"XXXX":0}}} <nil>
Nice! Exactly what we want!
But we're not done yet. What if we have a type with struct pointer fields? Like this:
type FooWithPtrs struct {
Str string `json:"String,omitempty"`
Int int `json:",omitempty"`
Bar *struct {
Float float64 `json:",omitempty"`
PtrInt int `json:",omitempty"`
Baz *struct {
X int `json:"XXXX,omitempty"`
} `json:",omitempty"`
} `json:",omitempty"`
}
Attempting to JSON-marshal a value of the duplicated type:
dupFooType := dupType(reflect.TypeOf(FooWithPtrs{}))
foo := reflect.Zero(dupFooType).Interface()
data, err := json.Marshal(foo)
fmt.Println("dup FooWithPtrs:\n", string(data), err)
Output:
dup FooWithPtrs:
{"String":"","Int":0,"Bar":null} <nil>
If the struct contains pointers, those appear in the JSON output as null
, but we would want their fields too in the output. That requires initializing them to non-nil
values so they generate output.
Luckily we can also do this using reflection:
func initPtrs(v reflect.Value) {
if !v.CanAddr() {
return
}
if v.Kind() == reflect.Pointer {
v.Set(reflect.New(v.Type().Elem()))
v = v.Elem()
}
if v.Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
initPtrs(v.Field(i))
}
}
}
We're excited! Let's see this in action:
dupFooType := dupType(reflect.TypeOf(FooWithPtrs{}))
fooVal := reflect.New(dupFooType)
initPtrs(fooVal.Elem())
data, err := json.Marshal(fooVal.Interface())
fmt.Println("dup and inited FooWithPtrs:\n", string(data), err)
Output:
dup and inited FooWithPtrs:
{"String":"","Int":0,"Bar":{"Float":0,"PtrInt":0,"Baz":{"XXXX":0}}} <nil>
Nice! It contains all fields!
Upvotes: 2