...

Source file src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry_examples_test.go

Documentation: go.mongodb.org/mongo-driver/bson/bsoncodec

     1  // Copyright (C) MongoDB, Inc. 2017-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package bsoncodec_test
     8  
     9  import (
    10  	"fmt"
    11  	"math"
    12  	"reflect"
    13  
    14  	"go.mongodb.org/mongo-driver/bson"
    15  	"go.mongodb.org/mongo-driver/bson/bsoncodec"
    16  	"go.mongodb.org/mongo-driver/bson/bsonrw"
    17  	"go.mongodb.org/mongo-driver/bson/bsontype"
    18  )
    19  
    20  func ExampleRegistry_customEncoder() {
    21  	// Create a custom encoder for an integer type that multiplies the input
    22  	// value by -1 when encoding.
    23  	type negatedInt int
    24  
    25  	negatedIntType := reflect.TypeOf(negatedInt(0))
    26  
    27  	negatedIntEncoder := func(
    28  		ec bsoncodec.EncodeContext,
    29  		vw bsonrw.ValueWriter,
    30  		val reflect.Value,
    31  	) error {
    32  		// All encoder implementations should check that val is valid and is of
    33  		// the correct type before proceeding.
    34  		if !val.IsValid() || val.Type() != negatedIntType {
    35  			return bsoncodec.ValueEncoderError{
    36  				Name:     "negatedIntEncoder",
    37  				Types:    []reflect.Type{negatedIntType},
    38  				Received: val,
    39  			}
    40  		}
    41  
    42  		// Negate val and encode as a BSON int32 if it can fit in 32 bits and a
    43  		// BSON int64 otherwise.
    44  		negatedVal := val.Int() * -1
    45  		if math.MinInt32 <= negatedVal && negatedVal <= math.MaxInt32 {
    46  			return vw.WriteInt32(int32(negatedVal))
    47  		}
    48  		return vw.WriteInt64(negatedVal)
    49  	}
    50  
    51  	reg := bson.NewRegistry()
    52  	reg.RegisterTypeEncoder(
    53  		negatedIntType,
    54  		bsoncodec.ValueEncoderFunc(negatedIntEncoder))
    55  
    56  	// Define a document that includes both int and negatedInt fields with the
    57  	// same value.
    58  	type myDocument struct {
    59  		Int        int
    60  		NegatedInt negatedInt
    61  	}
    62  	doc := myDocument{
    63  		Int:        1,
    64  		NegatedInt: 1,
    65  	}
    66  
    67  	// Marshal the document as BSON. Expect that the int field is encoded to the
    68  	// same value and that the negatedInt field is encoded as the negated value.
    69  	b, err := bson.MarshalWithRegistry(reg, doc)
    70  	if err != nil {
    71  		panic(err)
    72  	}
    73  	fmt.Println(bson.Raw(b).String())
    74  	// Output: {"int": {"$numberInt":"1"},"negatedint": {"$numberInt":"-1"}}
    75  }
    76  
    77  func ExampleRegistry_customDecoder() {
    78  	// Create a custom decoder for a boolean type that can be stored in the
    79  	// database as a BSON boolean, int32, or int64. For our custom decoder, BSON
    80  	// int32 or int64 values are considered "true" if they are non-zero.
    81  	type lenientBool bool
    82  
    83  	lenientBoolType := reflect.TypeOf(lenientBool(true))
    84  
    85  	lenientBoolDecoder := func(
    86  		dc bsoncodec.DecodeContext,
    87  		vr bsonrw.ValueReader,
    88  		val reflect.Value,
    89  	) error {
    90  		// All decoder implementations should check that val is valid, settable,
    91  		// and is of the correct kind before proceeding.
    92  		if !val.IsValid() || !val.CanSet() || val.Type() != lenientBoolType {
    93  			return bsoncodec.ValueDecoderError{
    94  				Name:     "lenientBoolDecoder",
    95  				Types:    []reflect.Type{lenientBoolType},
    96  				Received: val,
    97  			}
    98  		}
    99  
   100  		var result bool
   101  		switch vr.Type() {
   102  		case bsontype.Boolean:
   103  			b, err := vr.ReadBoolean()
   104  			if err != nil {
   105  				return err
   106  			}
   107  			result = b
   108  		case bsontype.Int32:
   109  			i32, err := vr.ReadInt32()
   110  			if err != nil {
   111  				return err
   112  			}
   113  			result = i32 != 0
   114  		case bsontype.Int64:
   115  			i64, err := vr.ReadInt64()
   116  			if err != nil {
   117  				return err
   118  			}
   119  			result = i64 != 0
   120  		default:
   121  			return fmt.Errorf(
   122  				"received invalid BSON type to decode into lenientBool: %s",
   123  				vr.Type())
   124  		}
   125  
   126  		val.SetBool(result)
   127  		return nil
   128  	}
   129  
   130  	reg := bson.NewRegistry()
   131  	reg.RegisterTypeDecoder(
   132  		lenientBoolType,
   133  		bsoncodec.ValueDecoderFunc(lenientBoolDecoder))
   134  
   135  	// Marshal a BSON document with a single field "isOK" that is a non-zero
   136  	// integer value.
   137  	b, err := bson.Marshal(bson.M{"isOK": 1})
   138  	if err != nil {
   139  		panic(err)
   140  	}
   141  
   142  	// Now try to decode the BSON document to a struct with a field "IsOK" that
   143  	// is type lenientBool. Expect that the non-zero integer value is decoded
   144  	// as boolean true.
   145  	type MyDocument struct {
   146  		IsOK lenientBool `bson:"isOK"`
   147  	}
   148  	var doc MyDocument
   149  	err = bson.UnmarshalWithRegistry(reg, b, &doc)
   150  	if err != nil {
   151  		panic(err)
   152  	}
   153  	fmt.Printf("%+v\n", doc)
   154  	// Output: {IsOK:true}
   155  }
   156  
   157  func ExampleRegistry_RegisterKindEncoder() {
   158  	// Create a custom encoder that writes any Go type that has underlying type
   159  	// int32 as an a BSON int64. To do that, we register the encoder as a "kind"
   160  	// encoder for kind reflect.Int32. That way, even user-defined types with
   161  	// underlying type int32 will be encoded as a BSON int64.
   162  	int32To64Encoder := func(
   163  		ec bsoncodec.EncodeContext,
   164  		vw bsonrw.ValueWriter,
   165  		val reflect.Value,
   166  	) error {
   167  		// All encoder implementations should check that val is valid and is of
   168  		// the correct type or kind before proceeding.
   169  		if !val.IsValid() || val.Kind() != reflect.Int32 {
   170  			return bsoncodec.ValueEncoderError{
   171  				Name:     "int32To64Encoder",
   172  				Kinds:    []reflect.Kind{reflect.Int32},
   173  				Received: val,
   174  			}
   175  		}
   176  
   177  		return vw.WriteInt64(val.Int())
   178  	}
   179  
   180  	// Create a default registry and register our int32-to-int64 encoder for
   181  	// kind reflect.Int32.
   182  	reg := bson.NewRegistry()
   183  	reg.RegisterKindEncoder(
   184  		reflect.Int32,
   185  		bsoncodec.ValueEncoderFunc(int32To64Encoder))
   186  
   187  	// Define a document that includes an int32, an int64, and a user-defined
   188  	// type "myInt" that has underlying type int32.
   189  	type myInt int32
   190  	type myDocument struct {
   191  		MyInt myInt
   192  		Int32 int32
   193  		Int64 int64
   194  	}
   195  	doc := myDocument{
   196  		Int32: 1,
   197  		Int64: 1,
   198  		MyInt: 1,
   199  	}
   200  
   201  	// Marshal the document as BSON. Expect that all fields are encoded as BSON
   202  	// int64 (represented as "$numberLong" when encoded as Extended JSON).
   203  	b, err := bson.MarshalWithRegistry(reg, doc)
   204  	if err != nil {
   205  		panic(err)
   206  	}
   207  	fmt.Println(bson.Raw(b).String())
   208  	// Output: {"myint": {"$numberLong":"1"},"int32": {"$numberLong":"1"},"int64": {"$numberLong":"1"}}
   209  }
   210  
   211  func ExampleRegistry_RegisterKindDecoder() {
   212  	// Create a custom decoder that can decode any integer value, including
   213  	// integer values encoded as floating point numbers, to any Go type
   214  	// with underlying type int64. To do that, we register the decoder as a
   215  	// "kind" decoder for kind reflect.Int64. That way, we can even decode to
   216  	// user-defined types with underlying type int64.
   217  	flexibleInt64KindDecoder := func(
   218  		dc bsoncodec.DecodeContext,
   219  		vr bsonrw.ValueReader,
   220  		val reflect.Value,
   221  	) error {
   222  		// All decoder implementations should check that val is valid, settable,
   223  		// and is of the correct kind before proceeding.
   224  		if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Int64 {
   225  			return bsoncodec.ValueDecoderError{
   226  				Name:     "flexibleInt64KindDecoder",
   227  				Kinds:    []reflect.Kind{reflect.Int64},
   228  				Received: val,
   229  			}
   230  		}
   231  
   232  		var result int64
   233  		switch vr.Type() {
   234  		case bsontype.Int64:
   235  			i64, err := vr.ReadInt64()
   236  			if err != nil {
   237  				return err
   238  			}
   239  			result = i64
   240  		case bsontype.Int32:
   241  			i32, err := vr.ReadInt32()
   242  			if err != nil {
   243  				return err
   244  			}
   245  			result = int64(i32)
   246  		case bsontype.Double:
   247  			d, err := vr.ReadDouble()
   248  			if err != nil {
   249  				return err
   250  			}
   251  			i64 := int64(d)
   252  			// Make sure the double field is an integer value.
   253  			if d != float64(i64) {
   254  				return fmt.Errorf("double %f is not an integer value", d)
   255  			}
   256  			result = i64
   257  		default:
   258  			return fmt.Errorf(
   259  				"received invalid BSON type to decode into int64: %s",
   260  				vr.Type())
   261  		}
   262  
   263  		val.SetInt(result)
   264  		return nil
   265  	}
   266  
   267  	reg := bson.NewRegistry()
   268  	reg.RegisterKindDecoder(
   269  		reflect.Int64,
   270  		bsoncodec.ValueDecoderFunc(flexibleInt64KindDecoder))
   271  
   272  	// Marshal a BSON document with fields that are mixed numeric types but all
   273  	// hold integer values (i.e. values with no fractional part).
   274  	b, err := bson.Marshal(bson.M{"myInt": float64(8), "int64": int32(9)})
   275  	if err != nil {
   276  		panic(err)
   277  	}
   278  
   279  	// Now try to decode the BSON document to a struct with fields
   280  	// that is type int32. Expect that the float value is successfully decoded.
   281  	type myInt int64
   282  	type myDocument struct {
   283  		MyInt myInt
   284  		Int64 int64
   285  	}
   286  	var doc myDocument
   287  	err = bson.UnmarshalWithRegistry(reg, b, &doc)
   288  	if err != nil {
   289  		panic(err)
   290  	}
   291  	fmt.Printf("%+v\n", doc)
   292  	// Output: {MyInt:8 Int64:9}
   293  }
   294  

View as plain text