Dynamic JSON schemas in Go (part 1) : optional fields

Jean-François Bustarret
Random Go tips
Published in
2 min readOct 29, 2015

It is sometimes necessary to work with JSON objects using a dynamic schema : optional fields, fields having dynamic types, …

In this first part, we’ll start with the easy part : generating JSON with optional fields. We’ll leave dynamic typing for later.

Let’s say we need to build a single Go type that encodes in two different ways. We need to keep zero values, omitempty is not an option.

Solution 1 : maps

Using a dynamic map[string]interface{} is an easy solution : it can map to any JSON schema.

But dynamic schemas/types/whatever easily break at runtime (because of typos in keys, values cast to a wrong type, …) whereas static ones have the huge advantage of being validated by the compiler (plus the additional bonus of enabling autocompletion in your favorite IDE).

In most cases, the gain of using a schema based on structs outweighs the cost of writing it.

Solution 2 : pointers

A pointer field can have 3 different states : missing (nil), present but zero and present and non zero.

Let’s define a struct that includes all possible fields, each optional field being a pointer (slices are already pointers, no need to use a pointer to a slice) :

http://play.golang.org/p/-PsQ55_ZcC

The first problem with this approach is that you cannot take the pointer of a string/integer literal, you’ll have to use intermediate variables in struct literals. But the main problem is that operations on pointers are painful :

Solution 3 : anonymous fields and pointers

You might remember that :

Anonymous struct fields are usually marshaled as if their inner exported fields were fields in the outer struct, subject to the usual Go visibility rules
https://golang.org/pkg/encoding/json/#Marshal

and

A field declared with a type but no explicit field name is an anonymous field, also called an embedded field or an embedding of the type in the struct. An embedded type must be specified as a type name T or as a pointer to a non-interface type name *T
https://golang.org/ref/spec#AnonymousField

When an anonymous pointer field is nil, no fields are added, and when it has a non nil value, all inner fields are inlined.

http://play.golang.org/p/g_jJ6-4QGj

Not perfect, because you’ll still have to test against nil pointers, but IMHO far better than the previous two solutions.

Please note that, depending on your requirements, it might be better to use two structs : one with a *Foo field, the other with *Bar.

--

--