[GODRIVER-1808] Unmarshaling BSON into a struct with interface fields containing concrete value types doesn't work as expected. Created: 11/Dec/20  Updated: 08/Jan/24

Status: Backlog
Project: Go Driver
Component/s: BSON
Affects Version/s: 1.4.4
Fix Version/s: 2.0.0

Type: Bug Priority: Major - P3
Reporter: C1tas Wcniwohcrf Assignee: Unassigned
Resolution: Unresolved Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

go 1.15.6


Attachments: File unmarshal_test.go    
Issue Links:
Duplicate
is duplicated by GODRIVER-2382 Unmarshaling BSON into a struct with ... Closed
Epic Link: Go Driver 2.0 BSON
Quarter: FY24Q3
Backwards Compatibility: Major Change

 Description   

when set slice point to struct. bson can not decode value in to the origin object

compare to json package. json works well

 

// code placeholder
 
type Data struct {
   Name string `bson:"name"`
}
 
type Demo struct {
   Total []Total `bson:"total"`
   Data  []Data  `bson:"data"`
}
 
var new = Demo{
   Total: []Total{
      {
         Count: 100,
      },
   },
   Data: []Data{
      {
         "Alice",
      },
   },
}
 
type receiver struct {
   Total interface{}
   Data  interface{}
}
 
func TestUnmarshalBson(t *testing.T) {
   // a,b not get the value I want
   a := make([]Total, 0)
   b := make([]Data, 0)
   recv := receiver{
      Total: &a,
      Data:  &b,
   }
}



 Comments   
Comment by Ed Pelc [ 07/Jul/23 ]

Ran into this when following the pattern from [eagain|https://eagain.net/articles/go-json-kind/] to init an interface field to bson/json.Raw. It'd be nice if there was a builtin option to register the above type so that we know there are tests for it as it is quite long.

type Envelope struct {
    Kind Kind
    Msg any
}
var raw json.RawMessage
env := Envelope{
	Msg: &raw,
}
if err := json.Unmarshal([]byte(input), &env); err != nil {
	log.Fatal(err)
}
msg := kindHandlers[env.Type]()
if err := json.Unmarshal(raw, msg); err != nil {
	log.Fatal(err)
} 

The bson equivalent doesn't work with above. You have to use an explicit `Msg bson.Raw` field on `Envelope` which requires a different type or defining a custom bson unmarshaler.

Comment by Sébastien GLON [ 09/Jun/23 ]

Hi,
I have created this small codec for the interfce type:

var TInterface = reflect.TypeOf((*interface{})(nil)).Elem()
 
// DefaultInterfaceCodec is the Codec used for interface{} values.
type DefaultInterfaceCodec struct {
}
 
var (
	defaultDefaultInterfaceCodec = NewDefaultInterfaceCodec()
 
	_ bsoncodec.ValueCodec = defaultDefaultInterfaceCodec
	// _ typeDecoder          = defaultDefaultInterfaceCodec
)
 
// NewDefaultInterfaceCodec returns a DefaultInterfaceCodec with options opts.
func NewDefaultInterfaceCodec(opts ...*bsonoptions.DefaultInterfaceCodecOptions) *DefaultInterfaceCodec {
	codec := DefaultInterfaceCodec{}
	return &codec
}
 
// EncodeValue is the ValueEncoderFunc for interface{}.
func (eic DefaultInterfaceCodec) EncodeValue(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
	return bsoncodec.ValueEncoderError{Name: "DefaultInterfaceEncodeValue", Types: []reflect.Type{TInterface}, Received: val}
}
 
// DecodeValue is the ValueDecoderFunc for interface{}.
func (eic DefaultInterfaceCodec) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
	if !val.CanSet() || val.Type() != TInterface {
		return bsoncodec.ValueDecoderError{Name: "DefaultInterfaceDecodeValue", Types: []reflect.Type{TInterface}, Received: val}
	}
 
	var newVal reflect.Value
 
	switch vrType := vr.Type(); vrType {
	case bsontype.Null:
		if err := vr.ReadNull(); err != nil {
			return err
		}
 
		newVal = reflect.Zero(val.Type())
	default:
		rtype, err := dc.Registry.LookupTypeMapEntry(vr.Type())
		if err != nil {
			return err
 
		}
 
		decoder, err := dc.Registry.LookupDecoder(rtype)
		if err != nil {
			return err
		}
		newVal = reflect.New(rtype).Elem()
		err = decoder.DecodeValue(dc, vr, newVal)
		if err != nil {
			return err
		}
	}
	val.Set(newVal)
	return nil
} 

And you can register it, like that:

	rb := bsoncodec.NewRegistryBuilder()
	defaultInterCodec := NewDefaultInterfaceCodec(
		bsonoptions.DefaultInterfaceCodec())
	bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
	bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
	rb.RegisterTypeMapEntry(bsontype.EmbeddedDocument, reflect.TypeOf(map[string]interface{}{}))
	rb.RegisterTypeMapEntry(bsontype.Array, reflect.TypeOf([]interface{}{}))
	rb.RegisterTypeDecoder(TInterface, defaultInterCodec) 

Do not lost the `RegisterTypeMapEntry` to force decode array and embeded docuement

Comment by Matt Dale [ 14/Oct/22 ]

Results of investigation:
Unmarshaling JSON into an instance of a struct with an empty interface field that has a value assigned to it unmarshals into the assigned value correctly. The BSON unmarshaler ignores the assigned value and only considers the field type in the struct definition, resulting into an unmarshal error.

E.g.

type foo struct {
	Val interface{}
}
a := foo{Val: ""}
b := foo{Val: 0}
// a and b should unmarshal using the type of the value assigned to Val, not interface{}

See StructCodec.describeStruct, which takes a reflect.Type instead of a reflect.Value, meaning it can't access the original value that might have additional type information. Additionally, the struct description caching (see here) complicates getting different values for the fields of the same struct type.

Confirmed this is still an issue as of Go Driver v1.10.2. The fix for this issue is to consider the type of the value stored in the interface field, if present, instead of just the field type.

Comment by Kevin Albertson [ 14/Dec/20 ]

Hi wangyuhengs@outlook.com, thank you for the report, and for the test script! Off-hand, I was able to run the script and observe the behavior you describe. This may not be a bug, and would be a backwards breaking change if we alter behavior of how the default decoder treats an empty interface. I think you could accomplish this by registering a custom BSON decoder (see go doc bsoncode for more info).

Generated at Thu Feb 08 08:37:10 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.