Say you are decoding a JSON object into a Go struct. It comes from a service that is not under your control, so you cannot do much about the schema. However, you want to encode it differently.
You could go wild with json.Marshaler
, but it has some drawbacks:
- complexity: adds lots of extra code for big structs
- memory usage: must be careful not to do needless allocations
To be fair, in most cases you can avoid allocations in your MarshalJSON()
,
but that may lead to even more complexity, which now sits in your code base
(instead of encoding/json
), so it’s your job to unit test it. And that’s even
more boring code to write.
Here are a few tricks to be used with big structs.
Omitting fields
Let’s say you have this struct:
What you want is to encode User
, but without the password
field. A simple
way to do that with struct composition would be to wrap it in another struct:
The trick here is that we never set the Password
property of the
PublicUser
, and since it is a pointer type, it will default to nil
, and it
will be omitted (because of omitempty
).
Note that there’s no need to declare the omit
type, we could have simply used
*struct{}
or even bool
or int
, but declaring the type makes it explicit
that we’re omitting that field from the output. Which built-in type we use does
not matter as long as it has a zero value that is recognised by the omitempty
tag.
We could have used only anonymous values:
Try it in the playground.
Also note that we only include a pointer to the original User
struct in our
wrapper struct. This indirection avoids having to allocate a new copy of
User
.
Adding extra fields
Adding fields is even simpler than omitting. To continue our previous example,
let’s hide the password but expose an additional token
property:
Try it in the playground.
Composing structs
This is handy when combining data coming from different services. For example,
here’s a BlogPost
struct that also contains analytics data:
Try it in the playground.
Splitting objects
This is the opposite of composing structs. Just like when encoding a combined structs, we can decode into a combined struct and use the values separately:
Try it in the playground.
Renaming fields
This one is a combination of removing fields and adding extra fields: we simply
remove the field and add it with a different json:
tag. This can be done with
pointer indirection to avoid allocating memory, although for small data types
the indirection overhead can cost the same amount of memory as it would cost to
create a copy of the field, plus the runtime overhead.
Here is an example where we rename two struct fields, using indirection for the nested struct and copying the integer:
Try it in the playground.
Note that this is only practical when you want to rename one or two fields in a big struct. When renaming all fields, it is often simpler (and cleaner) to just create a new object altogether (i.e. a serialiser) and avoid the struct composition.