in no particular order, here are some tricks i've learned recently
- json.RawMessage
- json.Unmarshal(bytes, &output)
- Envelopes (or Container structs for interfaces)
- Query Builders
- Almost Dynamic Functions
1. json.RawMessage
here's the deal, you can tell the json decoder to return partially decoded json:
var output map[string]json.RawMessage
err := json.Unmarshal(bytes, &output)
and similarly, you can tell the json encoder to include partially encoded json
type Buffer struct {
Fields json.RawMessage
}
bytes, err = json.Marshal(Buffer{Fields: rawJson})
this might not seem like much, on it's own, but it's really useful when you're doing other crimes
2. json.Unmarshal(bytes, &output)
normally in go, if you wanted to support different output types for a function, you'd have different methods with different types, but json.Unmarshal does it differently. it reflects upon the arguments to the function to work out what type you passed it.
as a result, you can unpack into lists, maps, as well as things like user defined structs
var mapObj map[string]string
json.Unmarshal(bytes, &mapObj)
var listObj []string
json.Unmarshal(bytes, &listObj)
this obj.Scan(&output) idiom is very common in go, and very useful too.
3. Envelopes (or Container structs for interfaces)
i wrote about this elsewhere, but if you have some interface type in go, it doesn't play well with the json library. for encoding, it looks at the concrete type underneath, and serializes that
for decoding, well, it just gives up: how does it know which struct to use to unpack the json into? there's no way to write an UnmarshalJSON method directly on an interface, only structs can have method implementations attached.
so you write a container type
type Container struct {
Inner InterfaceType
}
func (c *Container) UnmarshalJson(bytes []bytes) error {
// pick a struct that implements Container
// c.Inner = new(struct)
// return UnmarshalJson(bytes, c.Inner)
}
this trick can also help with self-descriptive json structures with a Kind field or similar. you decode the json, read the kind field and pick a struct, and then create the struct as you would otherwise.
4. Query Builders
this isn't as cool or unobvious as the other tricks, but it helped me with my crimes:
let's say you're writing something with &output arguments, and you want to chain up several calls together. it can get a little clumsy.
var output1 Output
process(&output)
var output2 Output2
output1.Foo(&output2)
unlike with normal output arguments, x := client.Thing(), you have to precisely know the output type to predeclare it. you could use var output Interface, but it doesn't make the code any nicer to work with, and the reflection code is more effort to make it work
the easy alternative is a query builder style api: you wrap the output in a container class with all the methods you need, including Scan to extract the contents
service.Lookup("name").Scan(&mystruct)
service.Lookup("name").Call(input).Scan(&myarray)
5. Almost Dynamic Functions
it's another go reflect trick. did you know you can dynamically create functions for a given type?
type Service struct {
Hello func()
}
func main() {
service := Service{}
hello := func(in []reflect.Value) []reflect.Value {
fmt.Print("hello")
return []reflect.Value{}
}
fn := reflect.ValueOf(&service.Hello).Elem()
v := reflect.MakeFunc(fn.Type(), hello)
fn.Set(v)
service.Hello()
}
