v0.8.3
v0.8.3 v0.13.0 master v0.12.2 v0.11.3 v0.10.2 v0.9.3

Changesets

Using maps as changesets
[edit]
You are looking at the docs for an older version (v0.8.3). The latest version is v0.13.0.

Occasionally you need to distinguish presence from nil (undefined vs null). In gqlgen we do this using maps:

type Query {
	updateUser(id: ID!, changes: UserChanges!): User
}

type UserChanges {
	name: String
	email: String
}

Then in config set the backing type to map[string]interface{}

models:
  UserChanges:
    model: "map[string]interface{}"

After running go generate you should end up with a resolver that looks like this:

func (r *queryResolver) UpdateUser(ctx context.Context, id int, changes map[string]interface{}) (*User, error) {
	u := fetchFromDb(id)
	/// apply the changes
	saveToDb(u)
	return u, nil
}

We often use the mapstructure library to directly apply these changesets directly to the object using reflection:


func ApplyChanges(changes map[string]interface{}, to interface{}) error {
	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		ErrorUnused: true,
		TagName:     "json",
		Result:      to,
		ZeroFields:  true,
		// This is needed to get mapstructure to call the gqlgen unmarshaler func for custom scalars (eg Date)
		DecodeHook: func(a reflect.Type, b reflect.Type, v interface{}) (interface{}, error) {
			if reflect.PtrTo(b).Implements(reflect.TypeOf((*graphql.Unmarshaler)(nil)).Elem()) {
				resultType := reflect.New(b)
				result := resultType.MethodByName("UnmarshalGQL").Call([]reflect.Value{reflect.ValueOf(v)})
				err, _ := result[0].Interface().(error)
				return resultType.Elem().Interface(), err
			}

			return v, nil
		},
	})

	if err != nil {
		return err
	}

	return dec.Decode(changes)
}