To upgrade imports of the Go Driver from v1 to v2, we recommend using marwan-at-work/mod :
mod upgrade --mod-name=github.com/pritunl/mongo-go-driver
The description package has been removed in v2.
References to description.Server and description.Topology have been replaced with event.ServerDescription and event.TopologyDescription, respectively. Additionally, the following changes have been made to the fields:
Kindhas been changed fromuint32tostringfor ease of use.SessionTimeoutMinuteshas been changed fromuint32to*int64to differentiate between a zero timeout and no timeout.
The following event constants have been renamed to match their string literal value:
PoolCreatedtoConnectionPoolCreatedPoolReadytoConnectionPoolReadPoolClearedtoConnectionPoolClearedPoolClosedEventtoConnectionPoolClosedGetStartedtoConnectionCheckOutStartedGetFailedtoConnectionCheckOutFailedGetSucceededtoConnectionCheckedOutConnectionReturnedtoConnectionCheckedIn
CommandFailedEvent.Failure has been converted from a string type to an error type to convey additional error information and to match the type of other *.Failure fields, like ServerHeartbeatFailedEvent.Failure.
The type for ServerConnectionID has been changed to *int64 to prevent an int32 overflow and to differentiate between an ID of 0 and no ID.
The type for ServerConnectionID has been changed to *int64 to prevent an int32 overflow and to differentiate between an ID of 0 and no ID.
DurationNanos has been removed in favor of Duration.
DurationNanos has been removed in favor of Duration.
Client.Connect() has been removed in favor of mongo.Connect(). See the section on NewClient for more details.
The context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose.
This example shows how to construct a session object from a context, rather than using a context to perform session operations.
// v1
client.UseSession(context.TODO(), func(sctx mongo.SessionContext) error {
if err := sctx.StartTransaction(options.Transaction()); err != nil {
return err
}
_, err = coll.InsertOne(context.TODO(), bson.D{{"x", 1}})
return err
})// v2
client.UseSession(context.TODO(), func(ctx context.Context) error {
sess := mongo.SessionFromContext(ctx)
if err := sess.StartTransaction(options.Transaction()); err != nil {
return err
}
_, err = coll.InsertOne(context.TODO(), bson.D{{"x", 1}})
return err
})This example shows how to migrate usage of the collection.Clone method, which no longer returns an error.
// v1
clonedColl, err := coll.Clone(options.Collection())
if err != nil {
log.Fatalf("failed to clone collection: %v", err)
}// v2
clonedColl := coll.Clone(options.Collection())The Distinct() collection method returns a struct that can be decoded, similar to Collection.FindOne. Instead of iterating through an untyped slice, users can decode same-type data using conventional Go syntax.
If the data returned is not same-type (i.e. name is not always a string) a user can iterate through the result directly as a bson.RawArray type:
// v1
filter := bson.D{{"age", bson.D{{"$gt", 25}}}}
values, err := coll.Distinct(context.TODO(), "name", filter)
if err != nil {
log.Fatalf("failed to get distinct values: %v", err)
}
people := make([]any, 0, len(values))
for _, value := range values {
people = append(people, value)
}
fmt.Printf("car-renting persons: %v\n", people)// v2
filter := bson.D{{"age", bson.D{{"$gt", 25}}}}
res := coll.Distinct(context.TODO(), "name", filter)
if err := res.Err(); err != nil {
log.Fatalf("failed to get distinct result: %v", err)
}
var people []string
if err := res.Decode(&people); err != nil {
log.Fatal("failed to decode distinct result: %v", err)
}
fmt.Printf("car-renting persons: %v\n", people)If the data returned is not same-type (i.e. name is not always a string) a user can iterate through the result directly as a bson.RawArray type:
// v2
filter := bson.D{{"age", bson.D{{"$gt", 25}}}}
distinctOpts := options.Distinct().SetMaxTime(2 * time.Second)
res := coll.Distinct(context.TODO(), "name", filter, distinctOpts)
if err := res.Err(); err != nil {
log.Fatalf("failed to get distinct result: %v", err)
}
rawArr, err := res.Raw()
if err != nil {
log.Fatalf("failed to get raw data: %v", err)
}
values, err := rawArr.Values()
if err != nil {
log.Fatalf("failed to get values: %v", err)
}
people := make([]string, 0, len(rawArr))
for _, value := range values {
people = append(people, value.String())
}
fmt.Printf("car-renting persons: %v\n", people)The documents parameter in the Collection.InsertMany function signature has been changed from an []any type to an any type. This API no longer requires users to copy existing slice of documents to an []any slice.
// v1
books := []book{
{
Name: "Don Quixote de la Mancha",
Author: "Miguel de Cervantes",
},
{
Name: "Cien años de soledad",
Author: "Gabriel García Márquez",
},
{
Name: "Crónica de una muerte anunciada",
Author: "Gabriel García Márquez",
},
}
booksi := make([]any, len(books))
for i, book := range books {
booksi[i] = book
}
_, err = collection.InsertMany(ctx, booksi)
if err != nil {
log.Fatalf("could not insert Spanish authors: %v", err)
}// v2
books := []book{
{
Name: "Don Quixote de la Mancha",
Author: "Miguel de Cervantes",
},
{
Name: "Cien años de soledad",
Author: "Gabriel García Márquez",
},
{
Name: "Crónica de una muerte anunciada",
Author: "Gabriel García Márquez",
},
}ListCollectionSpecifications() returns a slice of structs instead of a slice of pointers.
// v1
var specs []*mongo.CollectionSpecification
specs, _ = db.ListCollectionSpecifications(context.TODO(), bson.D{})// v2
var specs []mongo.CollectionSpecification
specs, _ = db.ListCollectionSpecifications(context.TODO(), bson.D{})This sentinel error has been removed from the mongo package. Users that need to check if a write operation was unacknowledged can do so by inspecting the Acknowledged field on the associated struct:
BulkWriteResultDeleteResultInsertManyResultInsertOneResultRewrapManyDataKeyResultSingleResult
// v1
res, err := coll.InsertMany(context.TODO(), books)
if errors.Is(err, mongo.ErrUnacknowledgedWrite) {
// Do something
}// v2
res, err := coll.InsertMany(context.TODO(), books)
if !res.Acknowledged {
// Do something
}DDL commands such as dropping a collection will no longer return ErrUnacknowledgedWrite, nor will they return a result type that can be used to determine acknowledgement. It is recommended not to perform DDL commands with an unacknowledged write concern.
Cursor.SetMaxTime has been renamed to Cursor.SetMaxAwaitTime, specifying the maximum time for the server to wait for new documents that match the tailable cursor query on a capped collection.
The gridfs package has been merged into the mongo package. Additionally, gridfs.Bucket has been renamed to mongo.GridFSBucket
// v1
var bucket gridfs.Bucket
bucket, _ = gridfs.NewBucket(db, opts)// v2
var bucket mongo.GridFSBucket
bucket, _ = db.GridFSBucket(opts)ErrWrongIndex has been renamed to the more intuitive ErrMissingChunk, which indicates that the number of chunks read from the server is less than expected.
// v1
n, err := source.Read(buf)
if errors.Is(err, gridfs.ErrWrongIndex) {
// Do something
}// v2
n, err := source.Read(buf)
if errors.Is(err, mongo.ErrMissingChunk) {
// Do something
}SetWriteDeadline methods have been removed from GridFS operations in favor of extending bucket methods to include a context.Context argument.
// v1
uploadStream, _ := bucket.OpenUploadStream("filename", uploadOpts)
uploadStream.SetWriteDeadline(time.Now().Add(2*time.Second))// v2
ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second)
defer cancel()
uploadStream, _ := bucket.OpenUploadStream(ctx, "filename", uploadOpts)Additionally, Bucket.DeleteContext(), Bucket.FindContext(), Bucket.DropContext(), and Bucket.RenameContext() have been removed.
mongo.IndexOptionsBuilder has been removed, use the IndexOptions type in the options package instead.
Dropping an index replies with a superset of the following message: {nIndexesWas: n}, where n indicates the number of indexes there were prior to removing whichever index(es) were dropped. In the case of DropAll this number is always m - 1, where m is the total number of indexes as you cannot delete the index on the _id field. Thus, we can simplify the DropAll method by removing the server response.
// v1
res, err := coll.Indexes().DropAll(context.TODO())
if err != nil {
log.Fatalf("failed to drop indexes: %v", err)
}
type dropResult struct {
NIndexesWas int
}
dres := dropResult{}
if err := bson.Unmarshal(res, &dres); err != nil {
log.Fatalf("failed to decode: %v", err)
}
numDropped := dres.NIndexWas
// Use numDropped// v2
// List the indexes
cur, err := coll.Indexes().List(context.TODO())
if err != nil {
log.Fatalf("failed to list indexes: %v", err)
}
numDropped := 0
for cur.Next(context.TODO()) {
numDropped++
}
if err := coll.Indexes().DropAll(context.TODO()); err != nil {
log.Fatalf("failed to drop indexes: %v", err)
}
// List the indexes
cur, err := coll.Indexes().List(context.TODO())
if err != nil {
log.Fatalf("failed to list indexes: %v", err)
}
// Use numDroppedDropping an index replies with a superset of the following message: {nIndexesWas: n}, where n indicates the number of indexes there were prior to removing whichever index(es) were dropped. In the case of DropOne this number is always 1 in non-error cases. We can simplify the DropOne method by removing the server response.
Updated to return a slice of structs instead of a slice of pointers. See the database analogue for migration guide.
NewClient has been removed, use the Connect function in the mongo package instead.
client, err := mongo.NewClient(options.Client())
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
if err := client.Connect(context.TODO()); err != nil {
log.Fatalf("failed to connect to server: %v", err)
}client, err := mongo.Connect(options.Client())
if err != nil {
log.Fatalf("failed to connect to server: %v", err)
}Uses of mongo.Session through driver constructors (such as client.StartSession) have been changed to return a pointer to a mongo.Session struct and will need to be updated accordingly.
// v1
var sessions []mongo.Session
for i := 0; i < numSessions; i++ {
sess, _ := client.StartSession()
sessions = append(sessions, sess)
}// v2
var sessions []*mongo.Session
for i := 0; i < numSessions; i++ {
sess, _ := client.StartSession()
sessions = append(sessions, sess)
}SingleResult.DecodeBytes has been renamed to the more intuitive SingleResult.Raw.
This example shows how to update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext.
// v1
mongo.WithSession(context.TODO(),sess,func(sctx mongo.SessionContext) error {
// Callback
return nil
})// v2
mongo.WithSession(context.TODO(),sess,func(ctx context.Context) error {
// Callback
return nil
})ClientOptions.AuthenticateToAnything was removed in v2 (it was marked for internal use in v1).
The following fields were removed because they are no longer supported by the server
FindOptions.Snapshot(4.0)FindOneOptions.Snapshot(4.0)IndexOptions.Background(4.2)
The Go Driver offers users the ability to pass multiple options objects to operations in a last-on-wins algorithm, merging data at a field level:
function MergeOptions(target, optionsList):
for each options in optionsList:
if options is null or undefined:
continue
for each key, value in options:
if value is not null or undefined:
target[key] = value
return target
Currently, the driver maintains this logic for every options type, e.g. MergeFindOptions. For v2, we’ve decided to abstract the merge functions by changing the options builder pattern to maintain a slice of setter functions, rather than setting data directly to an options object. Typical usage of options should not change, for example the following is still honored:
opts1 := options.Find().SetBatchSize(1)
opts2 := options.Find().SetComment("foo")
_, err := coll.Find(context.TODO(), bson.D{{"x", 1"}}, opts1, opts2)
if err != nil {
panic(err)
}There are two notable cases that will require a migration: (1) modifying options data after building, and (2) creating a slice of options objects.
The options builder is now a slice of setters, rather than a single options object. In order to modify the data after building, users will need to create a custom setter function and append the builder’s Opts slice:
// v1
opts := options.Find().SetBatchSize(1)
if opts.MaxAwaitTime == nil {
opts.MaxAwaitTime = &defaultMaxAwaitTime
}// v2
opts := options.Find().SetBatchSize(1)
maxAwaitTimeSetter := func(opts *options.FindOptions) error {
if opts.MaxAwaitTime == nil {
opts.MaxAwaitTime = &defaultMaxAwaitTime
}
return nil
}
opts.Opts = append(opts.Opts, maxAwaitTimeSetter)Using options created with the builder pattern as elements in a slice:
// v1
opts1 := options.Find().SetBatchSize(1)
opts2 := options.Find().SetComment("foo")
opts := []*options.FindOptions{opts1, opts2}
_, err := coll.Find(context.TODO(), bson.D{{"x", 1"}}, opts...)// v2
opts1 := options.Find().SetBatchSize(1)
opts2 := options.Find().SetComment("foo")
opts := []options.Lister[options.FindOptions]{opts1, opts2}
_, err := coll.Find(context.TODO(), bson.D{{"x", 1"}}, opts...)Since a builder is just a slice of option setters, users can create options directly from a builder:
// v1
opt := &options.FindOptions{}
opt.SetBatchSize(1)
return findOptionAdder{option: opt}// v2
var opts options.FindOptions
for _, set := range options.Find().SetBatchSize(1).Opts {
_ = set(&opts)
}
return findOptionAdder{option: &opts}The DeleteOptions has been separated into DeleteManyOptions and DeleteOneOptions to configure the corresponding DeleteMany and DeleteOne operations.
The following types are not valid for a findOne operation and have been removed:
BatchSizeCursorTypeMaxAwaitTimeNoCursorTimeout
The ArrayFilters struct type has been removed in v2. As a result, the ArrayFilters field in the FindOneAndUpdateOptions struct now uses the []any type. The ArrayFilters field (now of type []any) serves the same purpose as the Filters field in the original ArrayFilters struct.
The UpdateOptions has been separated into UpdateManyOptions and UpdateOneOptions to configure the corresponding UpdateMany and UpdateOne operations.
The ArrayFilters struct type has been removed in v2. As a result, the ArrayFilters fields in the new UpdateManyOptions and UpdateOneOptions structs (which replace the old UpdateOptions struct) now use the []any type. The ArrayFilters field (now of type []any) serves the same purpose as the Filters field in the original ArrayFilters struct.
All functions that merge options have been removed in favor of a generic solution. See GODRIVER-2696 for more information.
Users should time out operations using either the client-level operation timeout defined by ClientOptions.Timeout or by setting a deadline on the context object passed to an operation. The following fields and methods have been removed:
AggregateOptions.MaxTimeandAggregateOptions.SetMaxTimeClientOptions.SocketTimeoutandClientOptions.SetSocketTimeoutCountOptions.MaxTimeandCountOptions.SetMaxTimeDistinctOptions.MaxTimeandDistinctOptions.SetMaxTimeEstimatedDocumentCountOptions.MaxTimeandEstimatedDocumentCountOptions.SetMaxTimeFindOptions.MaxTimeandFindOptions.SetMaxTimeFindOneOptions.MaxTimeandFindOneOptions.SetMaxTimeFindOneAndReplaceOptions.MaxTimeandFindOneAndReplaceOptions.SetMaxTimeFindOneAndUpdateOptions.MaxTimeandFindOneAndUpdateOptions.SetMaxTimeGridFSFindOptions.MaxTimeandGridFSFindOptions.SetMaxTimeCreateIndexesOptions.MaxTimeandCreateIndexesOptions.SetMaxTimeDropIndexesOptions.MaxTimeandDropIndexesOptions.SetMaxTimeListIndexesOptions.MaxTimeandListIndexesOptions.SetMaxTimeSessionOptions.DefaultMaxCommitTimeandSessionOptions.SetDefaultMaxCommitTimeTransactionOptions.MaxCommitTimeandTransactionOptions.SetMaxCommitTime
This example illustrates how to define an operation-level timeout using v2, without loss of generality.
DefaultReadConcern, DefaultReadPreference, and DefaultWriteConcern are all specific to transactions started by the session. Rather than maintain three fields on the Session struct, v2 has combined these options into DefaultTransactionOptions which specifies a TransactionOptions object.
// v1
sessOpts := options.Session().SetDefaultReadPreference(readpref.Primary())// v2
txnOpts := options.Transaction().SetReadPreference(readpref.Primary())
sessOpts := options.Session().SetDefaultTransactionOptions(txnOpts)The Option type and associated builder functions have been removed in v2 in favor of a ReadConcern literal declaration.
This example shows how to update usage of New() and Level() options builder with a ReadConcern literal declaration.
// v1
localRC := readconcern.New(readconcern.Level("local"))// v2
localRC := &readconcern.ReadConcern{Level: "local"}The ReadConcernGetLevel() method has been removed. Use the ReadConcern.Level field to get the level instead.
The WTimeout field has been removed from the WriteConcern struct. Instead, users should define a timeout at the operation-level using a context object.
*Codec structs and New*Codec methods have been removed. Additionally, the correlated bson/bsonoptions package has been removed, so codecs are not directly configurable using *CodecOptions structs in Go Driver 2.0. To configure the encode and decode behavior, use the configuration methods on a bson.Encoder or bson.Decoder. To configure the encode and decode behavior for a mongo.Client, use options.ClientOptions.SetBSONOptions with BSONOptions.
This example shows how to set ObjectIDAsHex.
// v1
var res struct {
ID string
}
codecOpt := bsonoptions.StringCodec().SetDecodeObjectIDAsHex(true)
strCodec := bsoncodec.NewStringCodec(codecOpt)
reg := bson.NewRegistryBuilder().RegisterDefaultDecoder(reflect.String, strCodec).Build()
dc := bsoncodec.DecodeContext{Registry: reg}
dec, err := bson.NewDecoderWithContext(dc, bsonrw.NewBSONDocumentReader(DOCUMENT))
if err != nil {
panic(err)
}
err = dec.Decode(&res)
if err != nil {
panic(err)
}// v2
var res struct {
ID string
}
decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(DOCUMENT)))
decoder.ObjectIDAsHexString()
err := decoder.Decode(&res)
if err != nil {
panic(err)
}The RegistryBuilder struct and the bson.NewRegistryBuilder function have been removed in favor of (*bson.Decoder).SetRegistry and (*bson.Encoder).SetRegistry.
The StructTag struct as well as the StructTagParserFunc type have been removed. Therefore, users have to specify BSON tags manually rather than define custom BSON tag parsers.
The TransitionError struct has been merged into the bson package.
CodecZeroer and Proxy have been removed.
The bson/bsonrw package has been merged into the bson package.
As a result, interfaces ArrayReader, ArrayWriter, DocumentReader, DocumentWriter, ValueReader, and ValueWriter are located in the bson package now.
Interfaces BytesReader, BytesWriter, and ValueWriterFlusher have been removed.
Functions NewExtJSONValueReader and NewExtJSONValueWriter have been moved to the bson package as well.
Moreover, the ErrInvalidJSON variable has been merged into the bson package.
The bsonrw.NewBSONDocumentReader has been renamed to NewDocumentReader, which reads from an io.Reader, in the bson package.
The NewBSONValueReader has been removed.
This example creates a Decoder that reads from a byte slice.
// v1
b, _ := bson.Marshal(bson.M{"isOK": true})
decoder, err := bson.NewDecoder(bsonrw.NewBSONDocumentReader(b))// v2
b, _ := bson.Marshal(bson.M{"isOK": true})
decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(b)))The bsonrw.NewBSONValueWriter function has been renamed to NewDocumentWriter in the bson package.
This example creates an Encoder that writes BSON values to a bytes.Buffer.
// v1
buf := new(bytes.Buffer)
vw, err := bsonrw.NewBSONValueWriter(buf)
encoder, err := bson.NewEncoder(vw)// v2
buf := new(bytes.Buffer)
vw := bson.NewDocumentWriter(buf)
encoder := bson.NewEncoder(vw)The bson/mgocompat has been simplified. Its implementation has been merged into the bson package.
ErrSetZero has been renamed to ErrMgoSetZero in the bson package.
NewRegistryBuilder function has been simplified to NewMgoRegistry in the bson package.
Similarly, NewRespectNilValuesRegistryBuilder function has been simplified to NewRespectNilValuesMgoRegistry in the bson package.
The bson/primitive package has been merged into the bson package.
Additionally, the bson.D has implemented the json.Marshaler and json.Unmarshaler interfaces, where it uses a key-value representation in "regular" (i.e. non-Extended) JSON.
The bson.D.String and bson.M.String methods return an Extended JSON representation of the document.
// v2
d := D{{"a", 1}, {"b", 2}}
fmt.Printf("%s\n", d)
// Output: {"a":{"$numberInt":"1"},"b":{"$numberInt":"2"}}DefaultRegistry has been removed. Using bson.DefaultRegistry to either access the default registry behavior or to globally modify the default registry, will be impacted by this change and will need to configure their registry using another mechanism.
The NewRegistryBuilder function has been removed along with the bsoncodec.RegistryBuilder struct as mentioned above.
The BSON decoding logic has changed to decode into a bson.D by default.
The example shows the behavior change.
// v1
b1 := bson.M{"a": 1, "b": bson.M{"c": 2}}
b2, _ := bson.Marshal(b1)
b3 := bson.M{}
bson.Unmarshal(b2, &b3)
fmt.Printf("b3.b type: %T\n", b3["b"])
// Output: b3.b type: primitive.M// v2
b1 := bson.M{"a": 1, "b": bson.M{"c": 2}}
b2, _ := bson.Marshal(b1)
b3 := bson.M{}
bson.Unmarshal(b2, &b3)
fmt.Printf("b3.b type: %T\n", b3["b"])
// Output: b3.b type: bson.DUse Decoder.DefaultDocumentM() or set the DefaultDocumentM field of options.BSONOptions to always decode documents into the bson.M type.
The signature of NewDecoder has been updated without an error being returned.
NewDecoderWithContext has been removed in favor of using the SetRegistry method to set a registry.
Correspondingly, the following methods have been removed:
UnmarshalWithRegistryUnmarshalWithContextUnmarshalValueWithRegistryUnmarshalExtJSONWithRegistryUnmarshalExtJSONWithContext
For example, a boolean type can be stored in the database as a BSON boolean or 32/64-bit integer. Given a registry:
type lenientBool bool
lenientBoolType := reflect.TypeOf(lenientBool(true))
lenientBoolDecoder := func(
dc bsoncodec.DecodeContext,
vr bsonrw.ValueReader,
val reflect.Value,
) error {
// All decoder implementations should check that val is valid, settable,
// and is of the correct kind before proceeding.
if !val.IsValid() || !val.CanSet() || val.Type() != lenientBoolType {
return bsoncodec.ValueDecoderError{
Name: "lenientBoolDecoder",
Types: []reflect.Type{lenientBoolType},
Received: val,
}
}
var result bool
switch vr.Type() {
case bsontype.Boolean:
b, err := vr.ReadBoolean()
if err != nil {
return err
}
result = b
case bsontype.Int32:
i32, err := vr.ReadInt32()
if err != nil {
return err
}
result = i32 != 0
case bsontype.Int64:
i64, err := vr.ReadInt64()
if err != nil {
return err
}
result = i64 != 0
default:
return fmt.Errorf(
"received invalid BSON type to decode into lenientBool: %s",
vr.Type())
}
val.SetBool(result)
return nil
}
// Create the registry
reg := bson.NewRegistry()
reg.RegisterTypeDecoder(
lenientBoolType,
bsoncodec.ValueDecoderFunc(lenientBoolDecoder))For our custom decoder with such a registry, BSON 32/64-bit integer values are considered true if they are non-zero.
// v1
// Use UnmarshalWithRegistry
// Marshal a BSON document with a single field "isOK" that is a non-zero
// integer value.
b, err := bson.Marshal(bson.M{"isOK": 1})
if err != nil {
panic(err)
}
// Now try to decode the BSON document to a struct with a field "IsOK" that
// is type lenientBool. Expect that the non-zero integer value is decoded
// as boolean true.
type MyDocument struct {
IsOK lenientBool `bson:"isOK"`
}
var doc MyDocument
err = bson.UnmarshalWithRegistry(reg, b, &doc)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", doc)
// Output: {IsOK:true}// v1
// Use NewDecoderWithContext
// Marshal a BSON document with a single field "isOK" that is a non-zero
// integer value.
b, err := bson.Marshal(bson.M{"isOK": 1})
if err != nil {
panic(err)
}
// Now try to decode the BSON document to a struct with a field "IsOK" that
// is type lenientBool. Expect that the non-zero integer value is decoded
// as boolean true.
type MyDocument struct {
IsOK lenientBool `bson:"isOK"`
}
var doc MyDocument
dc := bsoncodec.DecodeContext{Registry: reg}
dec, err := bson.NewDecoderWithContext(dc, bsonrw.NewBSONDocumentReader(b))
if err != nil {
panic(err)
}
err = dec.Decode(&doc)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", doc)
// Output: {IsOK:true}// v2
// Use SetRegistry
// Marshal a BSON document with a single field "isOK" that is a non-zero
// integer value.
b, err := bson.Marshal(bson.M{"isOK": 1})
if err != nil {
panic(err)
}
// Now try to decode the BSON document to a struct with a field "IsOK" that
// is type lenientBool. Expect that the non-zero integer value is decoded
// as boolean true.
type MyDocument struct {
IsOK lenientBool `bson:"isOK"`
}
var doc MyDocument
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(b)))
dec.SetRegistry(reg)
err = dec.Decode(&doc)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", doc)
// Output: {IsOK:true}The SetContext method has been removed in favor of using SetRegistry to set the registry of a decoder.
The signature of SetRegistry has been updated without an error being returned.
The signature of Reset has been updated without an error being returned.
Decoder.DefaultDocumentD() has been removed since a document, including a top-level value (e.g. you pass in an empty interface value to Decode), is always decoded into a bson.D by default. Therefore, use Decoder.DefaultDocumentM() to always decode a document into a bson.M to avoid unexpected decode results.
Decoder.ObjectIDAsHexString() method enables decoding a BSON ObjectId as a hexadecimal string. Otherwise, the decoder returns an error by default instead of decoding as the UTF-8 representation of the raw ObjectId bytes, which results in a garbled and unusable string.
The signature of NewEncoder has been updated without an error being returned.
NewEncoderWithContext has been removed in favor of using the SetRegistry method to set a registry.
Correspondingly, the following methods have been removed:
MarshalWithRegistryMarshalWithContextMarshalAppendMarshalAppendWithRegistryMarshalAppendWithContextMarshalValueWithRegistryMarshalValueWithContextMarshalValueAppendWithRegistryMarshalValueAppendWithContextMarshalExtJSONWithRegistryMarshalExtJSONWithContextMarshalExtJSONAppendWithRegistryMarshalExtJSONAppendWithContext
Here is an example of a registry that multiplies the input value by -1 when encoding for a negatedInt.
type negatedInt int
negatedIntType := reflect.TypeOf(negatedInt(0))
negatedIntEncoder := func(
ec bsoncodec.EncodeContext,
vw bsonrw.ValueWriter,
val reflect.Value,
) error {
// All encoder implementations should check that val is valid and is of
// the correct type before proceeding.
if !val.IsValid() || val.Type() != negatedIntType {
return bsoncodec.ValueEncoderError{
Name: "negatedIntEncoder",
Types: []reflect.Type{negatedIntType},
Received: val,
}
}
// Negate val and encode as a BSON int32 if it can fit in 32 bits and a
// BSON int64 otherwise.
negatedVal := val.Int() * -1
if math.MinInt32 <= negatedVal && negatedVal <= math.MaxInt32 {
return vw.WriteInt32(int32(negatedVal))
}
return vw.WriteInt64(negatedVal)
}
// Create the registry.
reg := bson.NewRegistry()
reg.RegisterTypeEncoder(
negatedIntType,
bsoncodec.ValueEncoderFunc(negatedIntEncoder))Encode by creating a custom encoder with the registry:
// v1
// Use MarshalWithRegistry
b, err := bson.MarshalWithRegistry(reg, bson.D{{"negatedInt", negatedInt(1)}})
if err != nil {
panic(err)
}
fmt.Println(bson.Raw(b).String())
// Output: {"negatedint": {"$numberInt":"-1"}}// v1
// Use NewEncoderWithContext
buf := new(bytes.Buffer)
vw, err := bsonrw.NewBSONValueWriter(buf)
if err != nil {
panic(err)
}
ec := bsoncodec.EncodeContext{Registry: reg}
enc, err := bson.NewEncoderWithContext(ec, vw)
if err != nil {
panic(err)
}
err = enc.Encode(bson.D{{"negatedInt", negatedInt(1)}})
if err != nil {
panic(err)
}
fmt.Println(bson.Raw(buf.Bytes()).String())
// Output: {"negatedint": {"$numberInt":"-1"}}// v2
// Use SetRegistry
buf := new(bytes.Buffer)
vw := bson.NewDocumentWriter(buf)
enc := bson.NewEncoder(vw)
enc.SetRegistry(reg)
err := enc.Encode(bson.D{{"negatedInt", negatedInt(1)}})
if err != nil {
panic(err)
}
fmt.Println(bson.Raw(buf.Bytes()).String())
// Output: {"negatedint": {"$numberInt":"-1"}}The SetContext method has been removed in favor of using SetRegistry to set the registry of an encoder.
The signature of SetRegistry has been updated without an error being returned.
The signature of Reset has been updated without an error being returned.
A new RawArray type has been added to the bson package as a primitive type to represent a BSON array. Correspondingly, RawValue.Array() returns a RawArray instead of Raw.
The MarshalBSONValue method of the ValueMarshaler interface now returns a byte value representing the BSON type. That allows external packages to implement the ValueMarshaler interface without having to import the bson package. Convert a returned byte value to bson.Type to compare with the BSON type constants. For example:
btype, _, _ := m.MarshalBSONValue()
fmt.Println("type of data: %s: ", bson.Type(btype))
fmt.Println("type of data is an array: %v", bson.Type(btype) == bson.TypeArray)The UnmarshalBSONValue method of the ValueUnmarshaler interface now accepts a byte value representing the BSON type for the first argument. That allows packages to implement ValueUnmarshaler without having to import the bson package. For example:
if err := m.UnmarshalBSONValue(bson.TypeEmbeddedDocument, bytes); err != nil {
log.Fatalf("failed to decode embedded document: %v", err)
}