Custom marshalling of BSON, type as string

985 views Asked by At

I have a custom type for the purposes of formatting a time object

type MyTime time.Time

To use this with JSON I have implemented:

func (t *MyTime) UnmarshalJSON(b []byte) error
func (t MyTime) MarshalJSON() ([]byte, error)

These functions marshal the type to/from a formatted string and work fine. I now need to read/write this field to/from mongoDb. I have implemented

func (t *MyTime) UnmarshalBSON(b []byte) error

Which lets me read my type from mongo with no trouble (mongo doc has same format as above). this implementation simply strips the size and trailing zero bytes from the BSON string and parses as for JSON. So far so good My issue is how I write this value in the same format. I started by implementing:

func (t MyTime) MarshalBSON() ([]byte, error)

with a reversal of the unmarshal above (inserting size and trailing zero) and verified that the byte slice was identical to that I read earlier but this generated an error. Unlike the JSON version, the MarshalBSON implementation will not allow me to write a string. Debugging into the mgo library shows that it is expecting me to marshal a structure, not a string field. After googling the problem, I tried implementing

func (t *MyTime) GetBSON() (output interface{}, err error)

But this is not called by the InsertIntoMongo code. There are multiple ways of marshalling the object but this format is defined externally and I cannot change it.

How can I achieve this last piece of the puzzle? Namely, make BSON marshal my custom type as a string value (the reverse of the unmarshal already implemented)

edit: My implementation of MarshalBSON mirrored the JSON as a reverse of Unmarshal. In JSON unmarshal is passed a string (as []byte), Marshal returns the same string. In BSON, unmarshal is passed size (4 bytes = len(string) + 1) + string + \x00. Marshal returns the same stream (verified as identical)

the call

mongoClient.Database.Collection.InsertOne(ctx, data, nil)

Where data is a structure containing a number of instances of MyTime

error = go.mongodb.org/mongo-driver/mongo.CommandError
error.Code = 22
error.Name = InvalidBSON
error.Message = invalid bson type in element with field name '021-07-16T09:12:31.793395300' in object with _id: "..."

(The field name is the string value I tried to write with the first character (byte) missing) It should be a string, not a field With the Marshal Function missing, the parent element (MyTime) was written to mongo as an (empty) object where it should be a string.

1

There are 1 answers

0
Marrow父 On

Slighty old question, but in case its of help...

MarshalBSON() and UnmarshalBSON() are really for things that marshal/unmarshal to BSON document. Whereas, your type MyTime time.Time is probably better marshalled/unmarshalled as a BSON value - for which there are specific methods that need to be implemented, e.g.

func (mt *MyTime) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
    if btype != bsontype.String {
        return errors.New("cannot unmarshal non-string bson value to MyTime")
    }
    vr := bsonrw.NewBSONValueReader(btype, data)
    dec, err := bson.NewDecoder(vr)
    if err != nil {
        return err
    }
    var str string
    err = dec.Decode(&str)
    if err != nil {
        return err
    }
    dt, err := time.Parse(time.RFC3339, str)
    if err != nil {
        return err
    }
    *mt = MyTime(dt)
    return nil
}

func (mt MyTime) MarshalBSONValue() (bsontype.Type, []byte, error) {
    str := time.Time(mt).Format(time.RFC3339)
    return bson.MarshalValue(str)
}