UnmarshalBSON having different behavior than UnmarshalJSON (and erroring where it should not)

Hi there,

I am trying to work with an interface{}

I have an object of type foo containing an interface ActivationInterface ; this object is persisted and I have trouble fetching it back, as the underlying type of the inner object is not known.

I implemented UnmarshalBSON as follow without success ; it seems that even after setting the concrete type of the interface, the unmarshaler still does now the underlying type and I still get the error: error decoding key act: no decoder found for main.ActivationInterface

I tried replacing everything BSON related by JSON ; and it works.
Here are both examples:
Erroring UnmarshalBSON: Go Playground - The Go Programming Language
Almost the same code, but woking using UnmarshallJSON: Go Playground - The Go Programming Language

Do you have any idea on what is happening ?
Thanks !

Is this relevant? https://jira.mongodb.org/browse/GODRIVER-1182

Hey Jack, thanks for the help. It does not seem relevant as I think the problem is not the same. In my case, there is a difference of behavior between BSON and JSON when I recursively Unmarshal ; on the 2nd call, the type that I defined during the 1rst call for the interface, doesn’t seem to be set, as I still get an error.
It is not about having fields inlined :wink:
Thanks !

1 Like

I believe the root cause is that the Go driver BSON unmarshal logic doesn’t consider the value assigned to a struct field, only the type in the struct definition (e.g. Activation1 vs interface{}). I’ve created GODRIVER-2382 that describes the unexpected behavior vs the Go "encoding/json" behavior.

In the meantime, consider converting the data into a bson.Raw so you can look up the fields individually and unmarshal that way:

func (q *foo) UnmarshalBSON(data []byte) error {
	raw := bson.Raw(data)
	// Lookup the "type" value to figure out which activation
	// type to unmarshal.
	t, ok := raw.Lookup("type").StringValueOK()
	if !ok {
		return errors.New(`failed to find field "type"`)
	}
	q.Type = t

	// Lookup the "act" value as bytes so we can unmarshal it
	// into the correct activation type.
	actVal := raw.Lookup("act").Value
	if len(actVal) == 0 {
		return errors.New(`failed to find field "act"`)
	}

	switch t {
	case "act1":
		q.Act = new(Activation1)
	case "act2":
		q.Act = new(Activation2)
	default:
		return fmt.Errorf("unknown type %q", t)
	}

	return bson.Unmarshal(actVal, q.Act)
}

Check out the code integrated into your original example here.

1 Like

Hi Matt,

Thanks a lot for your help.
You indeed did put better words than mine on the unwanted behavior, thanks for filling the Jira ticket !
Your solution is the one I implemented to have my code working waiting for a more elegant way. As you can imagine, the example code I provided is not whole ; in the reality, foo contains more than 20 props that I copy one by one from a temporary type (used to replace the interface{} by Raw). That works but is ugly ;D

Thanks again, should I close this thread as solved and wait for the Jira ticket to be updated ?
Best regards,

@Vincent_Gamifly yeah, I think moving any conversation to the Jira ticket and closing this thread is the best idea. Thanks!

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.