struct2bson

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 5, 2023 License: MIT Imports: 5 Imported by: 0

README

Mongo Go - Struct to BSON

No longer actively maintained

This package is no longer actively maintained, please feel free to fork it if you would like to continue it's use.

Build Status codecov GoDoc

Provides utility methods to support the converting of structs to bson maps for use in various MongoDB queries/patch updates. It is intended to be used alongside the Mongo-Go Driver

Essentially it will convert a struct to bson.M

Original Source

This was built by stripping down and modifying Fatih Arslan & Cihangir Savas's Structs package.

Contents

Installation

go get github.com/naamancurtis/mongo-go-struct-to-bson

Basic Usage & Examples

To get started quickly simply import the package and set up the struct you want to be converted.

type User struct {
  ID        primitive.ObjectID `bson:"_id"`
  FirstName string `bson:"firstName"`

  // "omitempty" fields will not exist in the bson.M if they hold a zero value
  LastName  string `bson:"lastName,omitempty"`

  // "string" fields will hold the string value as defined by their implementation of the Stringer() interface
  DoB       time.Time `bson:"dob,string"`

  // "flatten" fields will have all of the fields within the nested struct moved up one level in the map to sit at the higher level
  Characteristics *Characteristics `bson:"characteristics,flatten"`

  // "omitnested" fields will not be recursively mapped, instead it will just hold the value
  Metadata  Metadata `bson:"metadata,omitnested"`

  // "-" fields will not be mapped at all
  Secret string `bson:"-"`

  // Unexported fields will not be mapped
  favouriteColor string
}

type Characteristics struct {
  LeftHanded bool `bson:"leftHanded"`
  Tall bool `bson:"tall"`
}

type Metadata struct {
  LastActive time.Time `bson:"lastActive"`
}

Then in order to map the struct you can use the convience method ConvertStructToBSONMap(), this handles the interim step of converting your struct to a StructToBSON struct (which is used as a wrapper to provide the relevant methods) and then converts the wrapped struct, returning a bson.M

user = User {
  ID:              "54759eb3c090d83494e2d804", // would actually hold the primitive.ObjectID value
  FirstName:       "Jane",
  LastName:        "",
  DoB:             time.Date(1985,6,15,0,0,0,0,time.UTC),
  Characteristics: &Characteristics{
    LeftHanded: true,
    Tall:       false,
  },
  Metadata:        Metadata{LastActive: time.Date(2019, 7,23, 14,0,0,0,time.UTC)},
  Secret:          "secret",
  favouriteColor:  "blue",
}

// Calling the Convert function - passing nil as the options for now.
result := struct2bson.ConvertStructToBSONMap(user, nil)

// The result would be:
bson.M {
  "_id": "54759eb3c090d83494e2d804", // would actually hold the primitive.ObjectID value
  "firstName": "Jane",
  "dob": "1985-06-15 00:00:00 +0000 UTC",
  "leftHanded": true,
  "tall": false,
  "metadata": Metadata {
    LastActive: time.Date(2019, 7,23, 14,0,0,0,time.UTC)
  },

  // Notes:
  // - dob: holds the Stringer() representation of time.Time
  // - Characterstics: struct has been flattened, so it's values are one level up
  // - Metadata: had the omitnested tag, so holds the actual value of the struct, not a bson.M
  // - lastName: has been omitted as it held the zero value for a string
  // - secret & favouriteColor: have been omitted, as they either had the "-" tag or were unexported
}
Calling ConvertStructToBSONMap with Options

There are currently 3 options available to pass to ConvertStructToBSONMap(), they're all held in a MappingOpts struct and default to a value of false if they're either unset or a value of nil is used as MappingOpts.

  1. UseIDifAvailable - Will just return bson.M { "_id": idVal } if the "_id" tag is present in that struct, if it is not present or holds a zero value it will map the struct as you would expect. This flag has priority over the other 3 options.
  2. RemoveID - Will remove any "_id" fields from your bson.M
  3. GenerateFilterOrPatch - If true, it will check all struct fields for zero type values and omit any that are found regardless of any tag options, effectively it enforces the behaviour of the "omitempty" tag, regardless of whether the struct field has it or not
Examples
// Using the same struct as the examples above

user = User {
  ID:              "54759eb3c090d83494e2d804",
  FirstName:       "Jane",
  LastName:        "",
  DoB:             time.Date(1985,6,15,0,0,0,0,time.UTC),
  Characteristics: &Characteristics{
    LeftHanded: true,
    Tall:       false,
  },
  Metadata:        Metadata{LastActive: time.Date(2019, 7, 23, 14, 0, 0, 0, time.UTC)},
  Secret:          "secret",
  favouriteColor:  "blue",
}
UseIDifAvailable = true
result := struct2bson.ConvertStructToBSONMap(user, &struct2bson.MappingOpts{UseIDifAvailable: true})

// result would be:
bson.M { "_id": "54759eb3c090d83494e2d804" }
// Value is indicative - it would actually hold the primitive.ObjectID value
RemoveID = true
result := struct2bson.ConvertStructToBSONMap(user, &struct2bson.MappingOpts{RemoveID: true})

// result would be:
bson.M {
  "firstName": "Jane",
  "dob": "1985-06-15 00:00:00 +0000 UTC",
  "leftHanded": true,
  "tall": false,
  "metadata": Metadata{LastActive: time.Date(2019, 7, 23, 14, 0, 0, 0, time.UTC)},
}
GenerateFilterOrPatch = true
// Using a modified user:
user = User {
  FirstName:       "Jane",
  LastName:        "",
  Characteristics: &Characteristics{
    LeftHanded: true,
    Tall:       false,
  },
  favouriteColor:  "blue",
}

result := struct2bson.ConvertStructToBSONMap(user, &struct2bson.MappingOpts{GenerateFilterOrPatch: true})

// result would be:
bson.M {
  "firstName": "Jane",
  "leftHanded": true,
}

// Note on the behaviour here: As go uses zero values, lastName & Characteristics.Tall
// are ignored, as they hold zero values.
// Please be aware of this when using this package
Using a different Tag Name

By default, the struct2bson uses the "bson" tag to identify what options and names should be assigned to each struct field. It is possible to change this to a custom tag if desired. In order to do so you need to split up the creation and mapping of your struct:

// 1. Create the struct wrapper
tempStruct := struct2bson.NewBSONstruct2bsonStruct(myStruct)

// 2. Set the custom tag name
tempStruct.SetTagName("customTag")

// 3. Convert the struct to bson.M
result := tempStruct.ToBSONMap(nil) // Passing nil as the options in this example

Known Issues

Zero Values

When using either the omitempty tag or MappingOpts{GenerateFilterOrPatch: true} and zero values mean something in your struct, ie. "" or false are valid, meaningful values for a field. You will have to work around the fact that they won't be included in your returned bson.M as they are identified as zero values and removed from the map.

One option is to add them manually to the returned bson.M once the mapping has occured using any checks you need to perform specific to your use case.

At the moment I can't work out an approach around this, however if anyone has any ideas then I'm all ears.

Getting involved

If you'd like to get involved or contribute to this package, please feel free to do so, whether it's recomendations, code improvements or additional functionality.

When making any changes, please make sure to fix/add any tests prior to submitting the PR - ideally I'd like to keep the test coverage to as close as 100% as possible.

Documentation

Overview

Provides utility methods to support the converting of structs to bson maps for use in various MongoDB queries/patch updates.

It is intended to be used alongside the Mongo-Go Driver

Index

Constants

This section is empty.

Variables

View Source
var (
	// By default, this package uses `bson` as the tag name
	// You can over-write this once you have wrapped your struct
	// in the mapping struct (StructToBSON) by chaining the
	// .SetTagName() call on the wrapped struct.
	DefaultTagName  = "bson"
	FallbackTagname = "json"
)

Functions

func ConvertStructToBSONMap

func ConvertStructToBSONMap(s interface{}, opts *MappingOpts) bson.M

ConvertStructToBSONMap wraps a struct and converts it to a BSON Map, factoring in any options passed as arguments By default, it uses the tag name `bson` on the struct fields to generate the map The mapping is recursive for any data structures contained within the struct

Example StructToBSON to be converted:

type ExampleStruct struct {
   Value1 string `bson:"myFirstValue"`
   Value2 []int `bson:"myIntSlice"`
}

The struct is first wrapped with the "StructToBSON" type to give access to the mapping functions and is then converted to a bson.M

bson.M {
   { Key: "myFirstValue", Value: "Example String" },
   { Key: "myIntSlice", Value: {1, 2, 3, 4, 5} },
}

The following tag options are factored into the parsing:

// "omitempty" - Omit if the value is the zero value
// "omitnested" - Pass the value of the struct directly as opposed to recursively mapping the struct
// "flatten" - Pull out the data from the nested struct up one level
// "string" - Use the implementation of the Stringer interface for the value
// "-" - Do not map this field

Types

type MappingOpts

type MappingOpts struct {
	// Will just return bson.M { "_id": idVal } if the "_id" tag is present in that struct,
	// if it is not present or holds a zero value it will map the struct as you would expect.
	// Setting true on this flag gives it priority over all other functionality.
	// ie. If "_id" is present, all other fields will be ignored
	//
	// This option is included in recursive calls, so if a nested struct
	// has an "_id" tag (and the top level struct didn't) then the
	// nested struct field in the bson.M will only hold the { "_id": idVal } result.
	//
	//   // Default: False
	UseIDifAvailable bool

	// Will remove any "_id" fields from your bson.M
	// Note: this will remove "_id" fields from nested data structures as well
	//
	// 	// Default: False
	RemoveID bool

	// If true, it will check all struct fields for zero type values and
	// omit any that are found regardless of any tag options, effectively it enforces
	// the behaviour of the "omitempty" tag, regardless of whether the struct field
	// has it or not
	//
	// This logic occurs after UseIDifAvailable & RemoveID
	//
	// 	// Default: False
	GenerateFilterOrPatch bool
}

MappingOpts allows the setting of options which drive the behaviour behind how the struct is parsed

type StructToBSON

type StructToBSON struct {
	TagName         string
	FallbackTagName string
	// contains filtered or unexported fields
}

StructToBson is the wrapper for a struct that enables this package to work

func NewBSONMapperStruct

func NewBSONMapperStruct(s interface{}) *StructToBSON

NewBSONMapperStruct returns the input struct wrapped by the mapper struct along with the tag name which should be parsed in the mapping

Panics if the argument is not a struct or pointer to a struct

func (*StructToBSON) SetTagName

func (s *StructToBSON) SetTagName(tag string)

SetTagName sets the tag name to be parsed

func (*StructToBSON) ToBSONMap

func (s *StructToBSON) ToBSONMap(opts *MappingOpts) bson.M

ToBSONMap parses all struct fields and returns a bson.M { tagName: value }. If there are nested structs it calls recursively maps them as well

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL