I'm learning go and stuck structs
I need to generate json:
{
"and" : [
{ "term" : { "name.second" : "ba" } }
]
}
}
So i can do it with code:
package main
import (
"encoding/json"
"fmt"
)
type Term map[string]interface{}
type TermHash struct {
Term `json:"term"`
}
type Filter struct {
And []TermHash `json:"and"`
}
func main() {
var filter Filter
filter.And = append(filter.And, TermHash{ Term{"name.second" : "ba"}})
jsonFilter, _ := json.MarshalIndent(filter, "", " ")
fmt.Printf(string(jsonFilter))
}
But I really do not want use separate TermHash and Term types, its seems unnecessary in code and used only to add this lines to filter. Can i avoid of using it?
I just want to accomplish this with only type Filter:
type Filter struct {
And []struct{
Term map[string]interface{} `json:"term"`
} `json:"and"`
}
This looks more readable and represent expected result, but I can't create instances of Term in this way. There is a way to add Terms line to json without creating separate types?
What you really want is a custom way to JSON-encode Term
. Rather than a simple map, it marshals as if it were an object containing a map. So, let's write that custom MarshalJSON
for it:
type Term map[string]interface{}
func (t Term) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
T map[string]interface{} `json:"term"`
}{t})
}
We create an anonymous struct
here, fill it with ourselves, and then marshal it to JSON. Note the use of map[string]interface{}
here. While that looks like Term
, it's actually a different type with its own way of being JSON encoded. If you tried to save some typing here and use T Term
, you'd find yourself in an infinite loop. (This idea of creating new types that have the same structure as other types is a major part of Go.)
Now our data structure is simple; just a slice of Term:
type Filter struct {
And []Term `json:"and"`
}
func main() {
var filter Filter
filter.And = append(filter.And, Term{"name.second" : "ba"})
jsonFilter, _ := json.MarshalIndent(filter, "", " ")
fmt.Printf(string(jsonFilter))
}
That said, you could also go the other way and make your data model more closely match the JSON. In that case Term
should be a struct, not a map. You'd probably write it this way:
type Term struct {
Values map[string]interface{} `json:"term"`
}
func NewTerm(key, value string) Term {
return Term{map[string]interface{}{key: value}}
}
type Filter struct {
And []Term `json:"and"`
}
func main() {
var filter Filter
filter.And = append(filter.And, NewTerm("name.second", "ba"))
jsonFilter, _ := json.MarshalIndent(filter, "", " ")
fmt.Printf(string(jsonFilter))
}
You can create a wrapper function around that.
package main
import (
"fmt"
"encoding/json"
)
type Filter struct {
And []map[string]interface{} `json:"and"`
}
func WrapFilter(i interface{}) Filter {
return Filter{
And: []map[string]interface{}{
map[string]interface{}{
"term": i,
},
},
}
}
func main() {
f := WrapFilter(map[string]string{"name.second": "ba"})
json.Marshal(f)
}
This way, you will always have "term"
has the second-level key in your marshaled JSON.