Июл
18
2022

Кастомные Marshaler и Unmarshaler для go mongo-driver

bson

В mongo-driver есть своя специфика реализации кодирования и декодирования данных. Если вы сделаете свой маршалер или анмаршалер не углубившись в детали реализации - скорее всего столкнетесь с ошибками.

Моя задача была сделать свою структуру для работы с датой на основе стандартного time.Time. Не реализовывая анмаршалер я получил ошибку что дата из базы данных не может раздекодиться в мою структуру с такой ошибкой: cannot decode UTC datetime into a my.Time.

Я начал делать маршалер и анмаршалер, но тоже столкнулся с ошибками. Я реализовал два метода:

func (t *Time) UnmarshalBSON([]byte) error {...}
func (t Time) MarshalBSON() ([]byte, error) {...}

Сколько не пытался на их основе что-то сделать - всё не выходило. И только по прошествии какого-то времени я понял:

В mongo-driver отдельные машралеры/анмаршалеры для объектов и для скалярных типов.

Так как я работаю с датой, это простой тип, без вложенных документов - мне надо реализовать маршалер и анмаршалер для значений, а это 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
Пожалуйста, оцените на сколько вам понравилась статья!
Голосов: 2 Среднее: 3