Mongo Go Driver Custom Decoder

In this article, I will show you how to custom the decode function of mongo go driver. We use it to decode the query result from mongodb to struct that we have already set.

Right now I have a task to migrate the web app, from Scala to Go. Everything went well until we found one problem, when we save the datetime data in mongodb and we want to display the data on unix timestamp.

Actually, before this pull request, it will show the unix time on the json response when we use primitive.DateTime type on the struct. But now, there is an override function for marshalling.

Anyway I use this sample of data.

{
        "_id" : ObjectId("607041f4576f81145415bc16"),
        "name" : "Linggar",
        "createdAt" : ISODate("2016-03-03T08:00:00Z")
}

I tried to create function to override json response, to convert the data from datetime type to integer.

type Timestamp time.Time

func (t Timestamp) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprint(t.Unix())), nil
}

type DataObj struct {
	ID        primitive.ObjectID `json:"_id" bson:"_id"`
	Name      string             `json:"name" bson:"name"`
	CreatedAt Timestamp          `json:"createdAt" bson:"createdAt"`
}

But when I decode the query result using this function.

cur.Decode(&result)

I got this error response.

error decoding key createdAt: cannot decode UTC datetime into a main.Timestamp

It seems mongo go driver cannot map the variable to the new type we create when it meet datetime type. But if we directly set the type to datetime on the struct, we didn’t find any error.

Actually we can set 2 structs to solve this problem, one struct for receiving the data from the query result, and one struct to reformat the data to the type we want. But I am just too lazy to write all struct twice 😛

After stuck with this problem for couple hours, finally I found the solution from the documentation! We can create custom decoder for one specific type. We create the registry and add the custom rule when unmarshalling the data with this function: bson.UnmarshalWithRegistry. To use that function, we have to decode to the data first to the bytes format with function DecodeBytes().

This is the code I use to decode and convert the data. First, we create the custom registry.

type Timestamp int64

type DataObj struct {
	ID        primitive.ObjectID `json:"_id" bson:"_id"`
	Name      string             `json:"name" bson:"name"`
	CreatedAt Timestamp          `json:"createdAt" bson:"createdAt"`
}

func createCustomRegistry() *bsoncodec.RegistryBuilder {
	var primitiveCodecs bson.PrimitiveCodecs
	rb := bsoncodec.NewRegistryBuilder()
	bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
	bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
	// register our new type
	myNumberType := reflect.TypeOf(Timestamp(0))
	// read the datetime type and convert to integer
	rb.RegisterTypeDecoder(
		myNumberType,
		bsoncodec.ValueDecoderFunc(func(_ bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
			// this is the function when we read the datetime format
			read, err := vr.ReadDateTime()
			if err != nil {
				return err
			}
			val.SetInt(read)
			return nil
		}),
	)
	primitiveCodecs.RegisterPrimitiveCodecs(rb)
	return rb
}

Then we decode the query and map the result.

var result DataObj
decoded, _ := cur.DecodeBytes()
var customRegistry = createCustomRegistry().Build()
err = bson.UnmarshalWithRegistry(customRegistry, []byte(decoded), &result)

And we will get the unix timestamp on the result!

$ go run main.go 
{"_id":"607041f4576f81145415bc16","name":"Linggar","createdAt":1456992000000}

Conclusion

Mongo go driver has so many functions to map and convert the data. We can create custom decoder and encoder to map the data to the type we want, without creating so many structs to decode and reformat the data.

Finally, if you can’t find any tutorial to solve your problem, please don’t give up, perhaps you can find your solution on the documentation 😀

You may also like

Leave a Reply

Your email address will not be published. Required fields are marked *