> Какой интерфейс реализовать для JSON маршаллинга и анмаршаллинга в Go и почему методы должны иметь ресивер по указателю (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Лига Цифровой Экономики
Стек: Go
> Пример ответа
Короткий ответ
Для JSON маршаллинга и анмаршаллинга в Go нужно реализовать интерфейсы json.Marshaler и json.Unmarshaler с методами MarshalJSON() ([]byte, error) и UnmarshalJSON([]byte) error соответственно. Ресивер по указателю обязателен для UnmarshalJSON, потому что метод должен мутировать получателя. Для MarshalJSON указатель тоже предпочтителен: это позволяет избежать копирования больших структур и гарантирует консистентность, если метод зависит от состояния, которое может меняться.
Подробное объяснение
Интерфейсы json.Marshaler и json.Unmarshaler - это стандартный механизм кастомизации JSON-сериализации в Go. Когда тип реализует MarshalJSON, пакет encoding/json вызывает этот метод вместо стандартного рефлексивного обхода структуры. Аналогично UnmarshalJSON перехватывает процесс десериализации.
Почему ресивер по указателю:
-
Мутация для
UnmarshalJSON- метод обязан изменять поля получателя. При ресивере по значению Go передаёт копию, и все изменения будут потеряны после возврата из метода. Это не просто рекомендация, а жёсткое требование компилятора: метод с ресивером по значению не удовлетворяет интерфейсуjson.Unmarshaler, потому чтоjson.Unmarshalработает с указателем на значение. -
Избежание копирования для
MarshalJSON- при маршаллинге больших структур копирование каждого элемента может быть дорогим. Ресивер по указателю позволяет работать с оригиналом. -
Консистентность состояния - если
MarshalJSONзависит от полей, которые могут быть изменены конкурентно, указатель даёт доступ к актуальному состоянию, а не к снимку на момент вызова. -
Семантика nil - при ресивере по указателю метод может корректно обрабатывать nil-получателя, возвращая
nullили ошибку. С ресивером по значению nil-случай вообще не может быть обработан.
Важно: если тип реализует MarshalJSON только на указателе, то маршаллинг значения (не указателя) будет использовать стандартное рефлексивное поведение, а не ваш метод. Это часто приводит к неожиданным багам.
На практике
В реальном коде обычно реализуют оба метода на указателе. Это даёт единообразное поведение и для значений, и для указателей. Если нужно, чтобы и значение, и указатель использовали кастомную сериализацию, можно определить метод на значении для MarshalJSON, но для UnmarshalJSON это невозможно - только указатель.
Частая практика - использовать кастомные типы-обёртки для полей с особыми требованиями (например, время в определённом формате, кастомные enum, числа с точностью). Также часто реализуют MarshalJSON для типов-алиасов, чтобы изменить формат без изменения исходной структуры.
Ещё один нюанс: если внутри MarshalJSON вы вызываете json.Marshal на самом типе, который реализует этот интерфейс, вы получите бесконечную рекурсию. Нужно использовать промежуточный тип-алиас без методов.
Пример кода
GOtype CustomTime struct {time.Time}// MarshalJSON - ресивер по указателюfunc (ct *CustomTime) MarshalJSON() ([]byte, error) {if ct == nil {return []byte("null"), nil}return json.Marshal(ct.Format("2006-01-02"))}// UnmarshalJSON - ресивер по указателю, обязателенfunc (ct *CustomTime) UnmarshalJSON(data []byte) error {var s stringif err := json.Unmarshal(data, &s); err != nil {return err}parsed, err := time.Parse("2006-01-02", s)if err != nil {return err}ct.Time = parsedreturn nil}type Event struct {ID int `json:"id"`Date CustomTime `json:"date"`}func main() {e := Event{ID: 1, Date: CustomTime{Time: time.Now()}}b, _ := json.Marshal(&e)fmt.Println(string(b)) // {"id":1,"date":"2025-01-15"}var e2 Eventjson.Unmarshal([]byte(`{"id":2,"date":"2025-01-16"}`), &e2)fmt.Println(e2.Date.Format("2006-01-02")) // 2025-01-16}
Как отвечать на собеседовании
Начните с прямого ответа: назовите интерфейсы и методы. Затем объясните, почему указатель - это не просто выбор, а необходимость для UnmarshalJSON. Подчеркните разницу между маршаллингом и анмаршаллингом: первый может работать и на значении, второй - только на указателе. Приведите пример из практики, где кастомная сериализация решает реальную проблему (формат даты, обработка null, совместимость с API). Упомяните про риск рекурсии при вызове json.Marshal внутри MarshalJSON и как его избежать через алиас типа. Если спросят про производительность, скажите, что указатель избегает копирования, но для маленьких структур это не критично.
Что проверяет интервьюер
Интервьюер проверяет:
- Понимание механизма интерфейсов в Go и того, как
encoding/jsonс ними взаимодействует. - Знание семантики ресиверов: мутация, копирование, nil-обработка.
- Умение предвидеть подводные камни: рекурсия, различие поведения для значения и указателя.
- Практический опыт: способность привести реальный кейс использования кастомной сериализации.
- Понимание trade-off между кастомной реализацией и стандартным рефлексивным поведением.
Типичные ошибки
- Реализация
UnmarshalJSONс ресивером по значению - код не компилируется или молча не работает. - Забывают про nil-получатель в
MarshalJSON, что приводит к panic при маршаллинге nil-указателя. - Вызов
json.MarshalвнутриMarshalJSONбез промежуточного типа - бесконечная рекурсия и stack overflow. - Реализация только
MarshalJSONбезUnmarshalJSON- асимметричное поведение при сериализации и десериализации. - Ожидание, что метод на указателе будет вызван для значения - это не так, нужно явно брать адрес.
- Игнорирование ошибок внутри
UnmarshalJSON- возврат nil при некорректных данных, что маскирует проблемы.
> Похожие задачи по Go
Используете ли вы Kanban или Scrum
Какая алгоритмическая сложность доступа по ключу в map Go
Почему вы сменили PHP на Go, что было мотиватором
Какие гарантии доставки сообщений предоставляет Google Pub/Sub?
> Похожие задачи по backend
Что такое protobuf и как он используется в gRPC
Что такое embedding
Как запустить две задачи параллельно и дождаться их результата
Вычистит ли сборщик мусора объекты, если они ссылаются друг на друга, но не имеют ссылок из корня
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью