I’m using MongoDB GoLang Driver v2 and I want to store a custom money type in MongoDB. If the value is nil, I want the BSON document to store price: null.
Example document I want:
{
"name": "Iphone",
"price": null,
"stock": 10
}
My code:
import (
"fmt"
money "github.com/Rhymond/go-money"
"go.mongodb.org/mongo-driver/v2/bson"
)
type moneyFields struct {
Amount int64 `json:"amount" bson:"amount"`
Currency string `json:"currency" bson:"currency"`
}
// Money wraps gomoney.Money for domain use
type Money struct {
// money.Money is of type: github.com/Rhymond/go-money
money *money.Money
}
type Product struct {
Name string `bson:"name"`
Price *Money `bson:"price"`
Stock int `bson:"stock"`
}
I implemented custom BSON marshaling:
func (m *Money) MarshalBSON() ([]byte, error) {
// Handle nil pointer case - marshal as BSON null
if m == nil {
// I have tried all these and none work:
return nil, nil
// return bson.Marshal(nil)
// return []byte{0x0A}, nil
}
doc := moneyFields{
Amount: m.money.Amount(),
Currency: m.money.Currency().Code,
}
data, err := bson.Marshal(doc)
if err != nil {
return nil, fmt.Errorf("failed to marshal Money: %w", err)
}
return data, nil
}
The problem
When Price is nil, I get errors such as:
Unrecognized BSON type 110 in element ...
mongo.MarshalError: bson.errNoEncoder {Type: reflect.Type nil}
I also tried implementing:
func (m *Money) MarshalBSONValue() (bson.Type, []byte, error)
But in MongoDB driver v2, this method never gets called, even though in v1 it does get called. Only MarshalBSON() is used.
My questions:
How do I correctly marshal a nil custom type as BSON null in MongoDB Go Driver v2?
Why does MarshalBSONValue never get called in v2?
What is the correct way to represent a nil custom struct pointer as BSON null in MongoDB v2?
I simply need:
*Money(nil) → BSON null
*Money{…} → BSON normal/embedded document
What is the correct way to do this in the v2 driver?
_, b, err := bson.Marshalvalue(bson.Null{})should probably do it.Marshalerinterface—„Marshaler is the interface implemented by types that can marshal themselves into a valid BSON document. Implementations ofMarshalermust return a full BSON document. To create custom BSON marshaling behavior for individual values in a BSON document, implement theValueMarshalerinterface instead.”—you need to implement it. I see you've tried to implement it, and say it never gets called but IMO this is a different problem (so ask another question) …bson.Marshaleris supposed to marshal a complete BSON document, whilebson.ValueMarshaleris for marshaling individual values (in a document). So to me, it looks like you somehow fail to keep that invariant. For a start, does yourMarshalBsonValueget called if you do something likevar m money.Money; doc := bson.D{bson.E{"foo": m}}; _ = bson.Marshal(&doc)?