# Migrating from 1.x to 2.0 To upgrade imports of the Go Driver from v1 to v2, we recommend using [marwan-at-work/mod ](https://github.com/marwan-at-work/mod): ``` mod upgrade --mod-name=go.mongodb.org/mongo-driver ``` ## Description Package The `description` package has been removed in v2. ## Event Package 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: - `Kind` has been changed from `uint32` to `string` for ease of use. - `SessionTimeoutMinutes` has been changed from `uint32` to `*int64` to differentiate between a zero timeout and no timeout. The following event constants have been renamed to match their string literal value: - `PoolCreated` to `ConnectionPoolCreated` - `PoolReady` to `ConnectionPoolRead` - `PoolCleared` to `ConnectionPoolCleared` - `PoolClosedEvent` to `ConnectionPoolClosed` - `GetStarted` to `ConnectionCheckOutStarted` - `GetFailed` to `ConnectionCheckOutFailed` - `GetSucceeded` to `ConnectionCheckedOut` - `ConnectionReturned` to `ConnectionCheckedIn` ### CommandFailedEvent `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`. ### CommandFinishedEvent 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. ### CommandStartedEvent 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. ### ServerHeartbeatFailedEvent `DurationNanos` has been removed in favor of `Duration`. ### ServerHeartbeatSucceededEvent `DurationNanos` has been removed in favor of `Duration`. ## Mongo Package ### Client #### Connect `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](https://github.com/mongodb/mongo-go-driver/blob/a76687682f080c9612295b646a00650d00dd16e1/x/mongo/driver/driver.go#L91) doesn’t accept a context, meaning that the context passed to `mongo.Connect()` in previous versions didn't serve a purpose. #### UseSession\[WithOptions\] This example shows how to construct a session object from a context, rather than using a context to perform session operations. ```go // 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 }) ``` ```go // 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 }) ``` ### Collection #### Clone This example shows how to migrate usage of the `collection.Clone` method, which no longer returns an error. ```go // v1 clonedColl, err := coll.Clone(options.Collection()) if err != nil { log.Fatalf("failed to clone collection: %v", err) } ``` ```go // v2 clonedColl := coll.Clone(options.Collection()) ``` #### Distinct 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: ```go // 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) ``` ```go // 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: ```go // 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) ``` #### InsertMany 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. ```go // 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) } ``` ```go // 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", }, } ``` ### Database #### ListCollectionSpecifications `ListCollectionSpecifications()` returns a slice of structs instead of a slice of pointers. ```go // v1 var specs []*mongo.CollectionSpecification specs, _ = db.ListCollectionSpecifications(context.TODO(), bson.D{}) ``` ```go // v2 var specs []mongo.CollectionSpecification specs, _ = db.ListCollectionSpecifications(context.TODO(), bson.D{}) ``` ### ErrUnacknowledgedWrite 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: - `BulkWriteResult` - `DeleteResult` - `InsertManyResult` - `InsertOneResult` - `RewrapManyDataKeyResult` - `SingleResult` ```go // v1 res, err := coll.InsertMany(context.TODO(), books) if errors.Is(err, mongo.ErrUnacknowledgedWrite) { // Do something } ``` ```go // 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 `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. ### GridFS The `gridfs` package has been merged into the `mongo` package. Additionally, `gridfs.Bucket` has been renamed to `mongo.GridFSBucket` ```go // v1 var bucket gridfs.Bucket bucket, _ = gridfs.NewBucket(db, opts) ``` ```go // v2 var bucket mongo.GridFSBucket bucket, _ = db.GridFSBucket(opts) ``` #### ErrWrongIndex `ErrWrongIndex` has been renamed to the more intuitive `ErrMissingChunk`, which indicates that the number of chunks read from the server is less than expected. ```go // v1 n, err := source.Read(buf) if errors.Is(err, gridfs.ErrWrongIndex) { // Do something } ``` ```go // v2 n, err := source.Read(buf) if errors.Is(err, mongo.ErrMissingChunk) { // Do something } ``` #### SetWriteDeadline `SetWriteDeadline` methods have been removed from GridFS operations in favor of extending bucket methods to include a `context.Context` argument. ```go // v1 uploadStream, _ := bucket.OpenUploadStream("filename", uploadOpts) uploadStream.SetWriteDeadline(time.Now().Add(2*time.Second)) ``` ```go // 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. ### IndexOptionsBuilder `mongo.IndexOptionsBuilder` has been removed, use the `IndexOptions` type in the `options` package instead. ### IndexView #### DropAll 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. ```go // 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 ``` ```go // 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 numDropped ``` #### DropOne 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 `DropOne` this number is always 1 in non-error cases. We can simplify the `DropOne` method by removing the server response. #### ListSpecifications Updated to return a slice of structs instead of a slice of pointers. See the [database analogue](#ListCollectionSpecifications) for migration guide. ### NewClient `NewClient` has been removed, use the `Connect` function in the `mongo` package instead. ```go 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) } ``` ```go client, err := mongo.Connect(options.Client()) if err != nil { log.Fatalf("failed to connect to server: %v", err) } ``` ### Session 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. ```go // v1 var sessions []mongo.Session for i := 0; i < numSessions; i++ { sess, _ := client.StartSession() sessions = append(sessions, sess) } ``` ```go // v2 var sessions []*mongo.Session for i := 0; i < numSessions; i++ { sess, _ := client.StartSession() sessions = append(sessions, sess) } ``` ### SingleResult `SingleResult.DecodeBytes` has been renamed to the more intuitive `SingleResult.Raw`. ### WithSession This example shows how to update the callback for `mongo.WithSession` to use a `context.Context` implementation, rather than the custom `mongo.SessionContext`. ```go // v1 mongo.WithSession(context.TODO(),sess,func(sctx mongo.SessionContext) error { // Callback return nil }) ``` ```go // v2 mongo.WithSession(context.TODO(),sess,func(ctx context.Context) error { // Callback return nil }) ``` ## Options Package `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) ### Options 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: ```pseudo 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](https://github.com/mongodb/mongo-go-driver/blob/2e7cb372b05cba29facd58aac7e715c3cec4e377/mongo/options/findoptions.go#L257). 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: ```go 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. #### Modifying Fields After Building 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: ```go // v1 opts := options.Find().SetBatchSize(1) if opts.MaxAwaitTime == nil { opts.MaxAwaitTime = &defaultMaxAwaitTime } ``` ```go // 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) ``` #### Creating a Slice of Options Using options created with the builder pattern as elements in a slice: ```go // 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...) ``` ```go // 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...) ``` #### Creating Options from Builder Since a builder is just a slice of option setters, users can create options directly from a builder: ```go // v1 opt := &options.FindOptions{} opt.SetBatchSize(1) return findOptionAdder{option: opt} ``` ```go // v2 var opts options.FindOptions for _, set := range options.Find().SetBatchSize(1).Opts { _ = set(&opts) } return findOptionAdder{option: &opts} ``` ### DeleteManyOptions / DeleteOneOptions The `DeleteOptions` has been separated into `DeleteManyOptions` and `DeleteOneOptions` to configure the corresponding `DeleteMany` and `DeleteOne` operations. ### FindOneOptions The following types are not valid for a `findOne` operation and have been removed: - `BatchSize` - `CursorType` - `MaxAwaitTime` - `NoCursorTimeout` ### FindOneAndUpdateOptions 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. ### UpdateManyOptions / UpdateOneOptions 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. ### Merge\*Options All functions that merge options have been removed in favor of a generic solution. See [GODRIVER-2696](https://jira.mongodb.org/browse/GODRIVER-2696) for more information. ### MaxTime 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.MaxTime` and `AggregateOptions.SetMaxTime` - `ClientOptions.SocketTimeout` and `ClientOptions.SetSocketTimeout` - `CountOptions.MaxTime` and `CountOptions.SetMaxTime` - `DistinctOptions.MaxTime` and `DistinctOptions.SetMaxTime` - `EstimatedDocumentCountOptions.MaxTime` and `EstimatedDocumentCountOptions.SetMaxTime` - `FindOptions.MaxTime` and `FindOptions.SetMaxTime` - `FindOneOptions.MaxTime` and `FindOneOptions.SetMaxTime` - `FindOneAndReplaceOptions.MaxTime` and `FindOneAndReplaceOptions.SetMaxTime` - `FindOneAndUpdateOptions.MaxTime` and `FindOneAndUpdateOptions.SetMaxTime` - `GridFSFindOptions.MaxTime` and `GridFSFindOptions.SetMaxTime` - `CreateIndexesOptions.MaxTime` and `CreateIndexesOptions.SetMaxTime` - `DropIndexesOptions.MaxTime` and `DropIndexesOptions.SetMaxTime` - `ListIndexesOptions.MaxTime` and `ListIndexesOptions.SetMaxTime` - `SessionOptions.DefaultMaxCommitTime` and `SessionOptions.SetDefaultMaxCommitTime` - `TransactionOptions.MaxCommitTime` and `TransactionOptions.SetMaxCommitTime` This example illustrates how to define an operation-level timeout using v2, without loss of generality. ### SessionOptions `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. ```go // v1 sessOpts := options.Session().SetDefaultReadPreference(readpref.Primary()) ``` ```go // v2 txnOpts := options.Transaction().SetReadPreference(readpref.Primary()) sessOpts := options.Session().SetDefaultTransactionOptions(txnOpts) ``` ## Read Concern Package The `Option` type and associated builder functions have been removed in v2 in favor of a `ReadConcern` literal declaration. ### Options Builder This example shows how to update usage of `New()` and `Level()` options builder with a `ReadConcern` literal declaration. ```go // v1 localRC := readconcern.New(readconcern.Level("local")) ``` ```go // v2 localRC := &readconcern.ReadConcern{Level: "local"} ``` ### ReadConcern.GetLevel() The `ReadConcernGetLevel()` method has been removed. Use the `ReadConcern.Level` field to get the level instead. ## Write Concern Package ### WTimeout The `WTimeout` field has been removed from the `WriteConcern` struct. Instead, users should define a timeout at the operation-level using a context object. ## Bsoncodecs / Bsonoptions Package `*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`. ```go // 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) } ``` ```go // 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) } ``` ### RegistryBuilder The `RegistryBuilder` struct and the `bson.NewRegistryBuilder` function have been removed in favor of `(*bson.Decoder).SetRegistry` and `(*bson.Encoder).SetRegistry`. ### StructTag / StructTagParserFunc 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. ### TransitionError The `TransitionError` struct has been merged into the bson package. ### Other interfaces removed from bsoncodes package `CodecZeroer` and `Proxy` have been removed. ## Bsonrw Package 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. ### NewBSONDocumentReader / NewBSONValueReader 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. ```go // v1 b, _ := bson.Marshal(bson.M{"isOK": true}) decoder, err := bson.NewDecoder(bsonrw.NewBSONDocumentReader(b)) ``` ```go // v2 b, _ := bson.Marshal(bson.M{"isOK": true}) decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(b))) ``` ### NewBSONValueWriter 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`. ```go // v1 buf := new(bytes.Buffer) vw, err := bsonrw.NewBSONValueWriter(buf) encoder, err := bson.NewEncoder(vw) ``` ```go // v2 buf := new(bytes.Buffer) vw := bson.NewDocumentWriter(buf) encoder := bson.NewEncoder(vw) ``` ## Mgocompat Package 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. ## Primitive 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. ```go // v2 d := D{{"a", 1}, {"b", 2}} fmt.Printf("%s\n", d) // Output: {"a":{"$numberInt":"1"},"b":{"$numberInt":"2"}} ``` ## Bson Package ### DefaultRegistry / NewRegistryBuilder `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. ### Decoder The BSON decoding logic has changed to decode into a `bson.D` by default. The example shows the behavior change. ```go // 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 ``` ```go // 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.D ``` Use `Decoder.DefaultDocumentM()` or set the `DefaultDocumentM` field of `options.BSONOptions` to always decode documents into the `bson.M` type. #### NewDecoder The signature of `NewDecoder` has been updated without an error being returned. #### NewDecoderWithContext `NewDecoderWithContext` has been removed in favor of using the `SetRegistry` method to set a registry. Correspondingly, the following methods have been removed: - `UnmarshalWithRegistry` - `UnmarshalWithContext` - `UnmarshalValueWithRegistry` - `UnmarshalExtJSONWithRegistry` - `UnmarshalExtJSONWithContext` For example, a boolean type can be stored in the database as a BSON boolean or 32/64-bit integer. Given a registry: ```go 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. ```go // 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} ``` ```go // 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} ``` ```go // 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} ``` #### SetContext The `SetContext` method has been removed in favor of using `SetRegistry` to set the registry of a decoder. #### SetRegistry The signature of `SetRegistry` has been updated without an error being returned. #### Reset The signature of `Reset` has been updated without an error being returned. #### DefaultDocumentD / DefaultDocumentM `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. #### ObjectIDAsHexString `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. ### Encoder #### NewEncoder The signature of `NewEncoder` has been updated without an error being returned. #### NewEncoderWithContext `NewEncoderWithContext` has been removed in favor of using the `SetRegistry` method to set a registry. Correspondingly, the following methods have been removed: - `MarshalWithRegistry` - `MarshalWithContext` - `MarshalAppend` - `MarshalAppendWithRegistry` - `MarshalAppendWithContext` - `MarshalValueWithRegistry` - `MarshalValueWithContext` - `MarshalValueAppendWithRegistry` - `MarshalValueAppendWithContext` - `MarshalExtJSONWithRegistry` - `MarshalExtJSONWithContext` - `MarshalExtJSONAppendWithRegistry` - `MarshalExtJSONAppendWithContext` Here is an example of a registry that multiplies the input value by -1 when encoding for a `negatedInt`. ```go 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: ```go // 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"}} ``` ```go // 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"}} ``` ```go // 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"}} ``` #### SetContext The `SetContext` method has been removed in favor of using `SetRegistry` to set the registry of an encoder. #### SetRegistry The signature of `SetRegistry` has been updated without an error being returned. #### Reset The signature of `Reset` has been updated without an error being returned. ### RawArray Type 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`. ### ValueMarshaler The `MarshalBSONValue` method of the [ValueMarshaler](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/bson#ValueMarshaler) interface now returns a `byte` value representing the [BSON type](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/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](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/bson#Type) to compare with the BSON type constants. For example: ```go 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) ``` ### ValueUnmarshaler The `UnmarshalBSONValue` method of the [ValueUnmarshaler](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/bson#ValueUnmarshaler) interface now accepts a `byte` value representing the [BSON type](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/bson#Type) for the first argument. That allows packages to implement `ValueUnmarshaler` without having to import the `bson` package. For example: ```go if err := m.UnmarshalBSONValue(bson.TypeEmbeddedDocument, bytes); err != nil { log.Fatalf("failed to decode embedded document: %v", err) } ```