1

I am accessing MongoDB using Go as follows:

var configRes *clientConfigData
err := clientDB.
    C(clientConfigCollection).
    Find(bson.M{}).
    One(&configRes)
if err != nil {
    return nil, errors.Wrap(err, "finding config collection")
}

Where

type clientConfigData struct {
    SMTPAssoc      int       `bson:"smtp_assoc"`
    PlanType       string    `bson:"plan_type"`
    EndDate        string    `bson:"end_date"`
}

Now since EndDate in MongoDB is stored as string so I declared EndDate as string. But I need to access this date as Go Time in clientConfigData.

Cœur
  • 37,241
  • 25
  • 195
  • 267

1 Answers1

2

If you want to change a value or do a type conversion when marshaling / unmarshaling your values from / to MongoDB, you may do it by implementing a custom marshaling / unmarshaling logic.

You can do this by implementing the bson.Getter and bson.Setter interfaces. Inside these methods, you may do whatever you want to with the values being marshaled / unmarshaled.

Easiest is to extend your clientConfigData type with an additional field, one that will be of type time.Time, the value you need:

type clientConfigData struct {
    SMTPAssoc  int       `bson:"smtp_assoc"`
    PlanType   string    `bson:"plan_type"`
    EndDateStr string    `bson:"end_date"`
    EndDate    time.Time `bson:"-"`
}

It has tag value bson:"-", because we don't want this to appear in MongoDB.

And now the custom marshaling / unmarhsaling logic:

const endDateLayout = "2006-01-02 15:04:05" // Use your layout here

func (c *clientConfigData) SetBSON(raw bson.Raw) (err error) {
    type my clientConfigData
    if err = raw.Unmarshal((*my)(c)); err != nil {
        return
    }
    c.EndDate, err = time.Parse(endDateLayout, c.EndDateStr)
    return
}

func (c *clientConfigData) GetBSON() (interface{}, error) {
    c.EndDateStr = c.EndDate.Format(endDateLayout)
    type my *clientConfigData
    return my(c), nil
}

What happens here is that SetBSON() is responsible to "populate" your struct value with the raw value coming from MongoDB, and GetBSON() is responsible to provide a value you want to be saved (marshaled).

When loading: SetBSON() first unmarshals the value as-is, then properly sets the EndDate field (which is of type time.Time) from the string date value that came from the DB (EndDateStr).

When saving: GetBSON() first fills the EndDateStr field (the one that is saved) from the EndDate field, and then simply returns, signaling that it is ok to save.

One thing to note: both SetBSON() and GetBSON() create a new my type inside them. The reason for this is to avoid stack overflow. Simply returning a value of type clientConfigData is bad, because we implemented bson.Getter and bson.Setter, so SetBSON() and GetBSON() would get called endlessly. The new my type does not have these methods, so endless "recursion" does not happen (the type keyword creates a new type, and it does not "inherit" methods of the underlying type).

Also see related / similar question: Set default date when inserting document with time.Time field

Community
  • 1
  • 1
icza
  • 389,944
  • 63
  • 907
  • 827
  • so If we didn't implement `bson.Getter` and `bson.Setter` we can simply return the `clienConfigData` right? but in this case we better not. Is this correct? – Gujarat Santana Feb 20 '17 at 13:00
  • 1
    Your question doesn't really make sense, as adding `GetBSON()` and `SetBSON()` **is** implementing `Getter` and `Setter` (`Getter` and `Setter` are interfaces defining `GetBSON()` and `SetBSON()`). If you don't add those methods, there isn't something to return from. – icza Feb 20 '17 at 13:06
  • Thanks a lot @icza – Syed Qasim Rizvi Feb 21 '17 at 06:33
  • @icza I have just one question, how to do the error handling for the error returning from `SetBSON`. – Syed Qasim Rizvi Feb 21 '17 at 06:36
  • @SyedQasimRizvi You don't handle it as you are not the one calling `SetBSON()`. It's the `mgo` package that –when loading values from MongoDB– checks if the value implements `Setter`, and if so, it calls its `SetBSON()` to do the unmarshaling. It's the `mgo` package that needs to handle the `error` returned by it, which may be wrapped, logged or returned by the outer function that you called (e.g. `Find().All()` or `Find().One()`). – icza Feb 21 '17 at 08:39