В mongo-driver есть своя специфика реализации кодирования и декодирования данных. Если вы сделаете свой маршалер или анмаршалер не углубившись в детали реализации - скорее всего столкнетесь с ошибками.
Моя задача была сделать свою структуру для работы с датой на основе стандартного time.Time. Не реализовывая анмаршалер я получил ошибку что дата из базы данных не может раздекодиться в мою структуру с такой ошибкой: cannot decode UTC datetime into a my.Time.
Я начал делать маршалер и анмаршалер, но тоже столкнулся с ошибками. Я реализовал два метода:
func (t *Time) UnmarshalBSON([]byte) error {...}
func (t Time) MarshalBSON() ([]byte, error) {...}
Сколько не пытался на их основе что-то сделать - всё не выходило. И только по прошествии какого-то времени я понял:
Так как я работаю с датой, это простой тип, без вложенных документов - мне надо реализовать маршалер и анмаршалер для значений, а это MarshalBSONValue и UnmarshalBSONValue.
ValueMarshaler
Тут всё просто, берем нашу дату стандартную и переводим её в байты через готовый метод. Он сам определит что это дата.
import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
func (t Time) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.MarshalValue(t.Time)
}
Стоит заметить, что тут мы не используем указатель на нашу структуру Time, а передаем копию - только так реализуется интерфейс.
ValueUnmarshaler
Тут немного сложнее, как как в зависимости от типа значения следует вызывать разные методы у читателя, который мы создаем на основе входных данных. Нам надо вызвать ReadDateTime, который возвращает дату в unix timestamp, из которого еще требуется создать объект даты.
import (
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
// UnmarshalBSONValue преобразует дату из MongoDB в тип Time
func (t *Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error {
vr := bsonrw.NewBSONValueReader(bt , data)
unixMillis, err := vr.ReadDateTime()
if err != nil {
return fmt.Errorf("reading date: %w", err)
}
t.Time = time.UnixMilli(unixMillis)
return nil
}
Такие методы есть у читателя:
ReadArray() (ArrayReader, error)
ReadBinary() (b []byte, btype byte, err error)
ReadBoolean() (bool, error)
ReadDocument() (DocumentReader, error)
ReadCodeWithScope() (code string, dr DocumentReader, err error)
ReadDBPointer() (ns string, oid primitive.ObjectID, err error)
ReadDateTime() (int64, error)
ReadDecimal128() (primitive.Decimal128, error)
ReadDouble() (float64, error)
ReadInt32() (int32, error)
ReadInt64() (int64, error)
ReadJavascript() (code string, err error)
ReadMaxKey() error
ReadMinKey() error
ReadNull() error
ReadObjectID() (primitive.ObjectID, error)
ReadRegex() (pattern, options string, err error)
ReadString() (string, error)
ReadSymbol() (symbol string, err error)
ReadTimestamp() (t, i uint32, err error)
ReadUndefined() error