Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MapstructureUnmarshaler interface to allow types to decide how to be unmarshalled #35

Open
chancez opened this issue Jan 24, 2025 · 0 comments

Comments

@chancez
Copy link

chancez commented Jan 24, 2025

In cilium/cilium we have a few custom cell.DecodeHooks for unmarshalling values into custom types: https://github.com/cilium/cilium/blob/main/pkg/hive/hive.go#L105-L132

Unfortunately, this requires centralizing the configuration of how to handle the custom types and they run after the default hooks: https://github.com/cilium/hive/blob/main/cell/config.go#L126

I propose we add an interface to give types the ability to define their own method of unmarshalling:

type MapstructureUnmarshaler interface {
	UnmarshalMapstructure(any) error
}

Then, we could define a new mapstructure.DecodeHookFunc:

func MapstructureUnmarshalerHook() mapstructure.DecodeHookFunc {
	// Loosely adapted from the json.Unmarshal implementation.
	// Specifically, the indirect() function:
	// https://cs.opensource.google/go/go/+/refs/tags/go1.21.6:src/encoding/json/decode.go;l=426
	return func(from, to reflect.Value) (interface{}, error) {
		// If v is a named type and is addressable,
		// start with its address, so that if the type has pointer methods,
		// we find them.
		if to.Kind() != reflect.Pointer && to.CanAddr() {
			to = to.Addr()
		}

		// Still not a pointer, so no point in continuing
		if to.Kind() != reflect.Pointer {
			return from.Interface(), nil
		}

		// In order for the UnmarshalMapstructure to assign to 'to', then it needs
		// to be non-nil
		if to.IsNil() {
			to.Set(reflect.New(to.Type().Elem()))
		}

		// convert to the interface, if it's not the desired interface, return the
		// value unchanged
		u, ok := to.Interface().(MapstructureUnmarshaler)
		if !ok {
			return from.Interface(), nil
		}

		// unmarshal "from" into 'to' using
		if err := u.UnmarshalMapstructure(from.Interface()); err != nil {
			return nil, err
		}

		return u, nil
	}
}

This would enable removing the hooks here: https://github.com/cilium/cilium/blob/main/pkg/hive/hive.go#L105-L132 and instead the logic could be implemented as methods on the types themselves. In the case of protobuf types, it would require a wrapper type to implement the method on, but I think that's acceptable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant