1

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?

5
  • 1
    _, b, err := bson.Marshalvalue(bson.Null{}) should probably do it. Commented Nov 26 at 17:02
  • @kostix Similar err: ` (InvalidBSON) Unrecognized BSON type 110 in element with field name 'items.0.base_price.tity' in object with _id: ObjectId('6927eb641fed3703c475376b')` Updated code: func (m *Money) MarshalBSON() ([]byte, error) { if m == nil || m.money == nil { _, b, err := bson.MarshalValue(bson.Null{}) if err != nil { return nil, err } return b, nil } // More Code } Commented 2 days ago
  • [1/2] Given this documentation for the Marshaler interface—„Marshaler is the interface implemented by types that can marshal themselves into a valid BSON document. Implementations of Marshaler must return a full BSON document. To create custom BSON marshaling behavior for individual values in a BSON document, implement the ValueMarshaler interface 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) … Commented 2 days ago
  • [2/2] …giving more details with more context. To better explain my point: bson.Marshaler is supposed to marshal a complete BSON document, while bson.ValueMarshaler is for marshaling individual values (in a document). So to me, it looks like you somehow fail to keep that invariant. For a start, does your MarshalBsonValue get called if you do something like var m money.Money; doc := bson.D{bson.E{"foo": m}}; _ = bson.Marshal(&doc)? Commented 2 days ago
  • 1
    @kostix Thanks. I have found the answer. The Interface in V2 has changed as stated by @ SHARIF NAWAZ . Commented 2 days ago

1 Answer 1

2

In MongoDB Go Driver v2, the old v1 method signatures are not used. You must implement the v2 versions of MarshalBSONValue and UnmarshalBSONValue.

Minimal working example (v2) MarshalBSONValue encodes Money for MongoDB (driver v2):

func (m *Money) MarshalBSONValue() (byte, []byte, error) {
    if m == nil || m.money == nil {
        return byte(bson.TypeNull), nil, nil // store as BSON null
    }

    // marshal embedded document...
    data, _ := bson.Marshal(moneyFields{/*...*/})
    return byte(bson.TypeEmbeddedDocument), data, nil
}

UnmarshalBSONValue decodes Money for MongoDB (driver v2):

func (m *Money) UnmarshalBSONValue(t byte, data []byte) error {
    if bson.Type(t) == bson.TypeNull {
        m.money = nil
        return nil
    }

    if bson.Type(t) != bson.TypeEmbeddedDocument {
        return fmt.Errorf("invalid BSON type for Money: %v", bson.Type(t))
    }

    // unmarshal embedded document...
    var mf moneyFields
    _ = bson.Unmarshal(data, &mf)
    // Add your code here
    return nil
}

Summary:
- v1 marshaling interfaces are ignored in driver v2.
- Implementing the v2 signatures fixes the issue.
- Money(nil) now correctly stores as BSON null.
- *Money{…} → BSON normal/embedded document

New contributor
SHARIF NAWAZ is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.