Значимо изменить маршализацию структуры в json можно только через метод MarshalJSON(), написав там полную реализацию маршализации. Как именно? На это документация Go ни ответов, ни рекомендаций не даёт, предоставляя, так сказать, полную свободу. А как воспользоваться этой свободой так, чтобы не нагородить кучу костылей, перенеся всю логику в MarshalJSON(), и потом не перепиливать эту бедную постоянно разрастающуюся функцию при очередных кастомизациях json?
  Решение на самом деле простое:
 (Второго пункта не будет, первого хватит.)
 Именно этот подход избавит от тонны говнокода запутанного кода, массы переделок и известного рода веселья перед важным релизом. Давайте посмотрим не на пример в документации, где кастомизируется json для простого int, и всей логики модели на несколько строк, а на нашу исходную задачу.
 Действительно ли нам нужно изменить структуру нашего объекта и напихать кучу костылей? Действительно ли нам вдруг стала мешать строгость языка, которая предусматривает взаимнооднозначное соответствие атрибутов json и самой структуры?
 Исходная задача — получить такие-то JSON структуры каких-то утверждённых форматов. В исходной задаче про костыли ничего не сказано. Сказано про разные структуры данных. А у нас для хранения этих данных используется один и тот же тип данных (struct). Таким образом наша единая сущность должна иметь несколько представлений. Вот мы и получили правильную интерпретацию задачи.
 Нужно сделать несколько представлений для нашего типа данных. Не менять преобразование в json для конкретного случая, а в принципе иметь несколько представлений, одно из которых является представлением по умолчанию.
 Итак, у нас появляется ещё одна сущность — представление.
 И давайте уже к примерам и, собственно, к коду.
 Пусть у нас есть книжный магазин, который продаёт книги. Всё у нас построено на микросервисах, и один из них отдаёт данные по запросам в формате json. Книги сначала выгружались только на витрину сайта. Потом мы подключись к разного рода партнёрским сетям, и, предоставляем, например, учащимся университетов книги по специальной цене. А недавно, наши маркетологи вдруг решили проводить какие-то промо-акции и им тоже нужна своя цена и ещё какой-то свой текст. Вычислением цен и подготовкой текстов пусть занимается какой-то другой микросервис, который складывает уже готовые данные в базу данных.
 Итак, эволюция нашей модели книги дошла до такого безобразия:
 Последний атрибут (view) — неэкспортируемый (приватный), он не является частью данных, а является местом хранения того самого представления, в котором и содержится информация, в какой json сворачиваться объекту. В простейшем случае это просто interface{}
 Мы также можем добавить в интерфейс нашего представления какой-либо метод, например Prepare(), который будет вызываться в MarshalJSON() и как-то подготавливать, валидировать, или логировать выходную структуру.
 Теперь давайте опишем наши представления и саму функцию
 Отправка и приём данных между структурами происходит по принципу совпадения названий атрибутов, при этом типы не обязательно должны точно совпадать, можно, например, отправлять из int64, а принимать в int, но не в uint.
 Последним шагом делаем маршализацию установленного представления с данными, используя всю мощь стандартного описания через теги json (`json:"promo,omitempty"`) 
 Очень важным требованием применением такого подхода является обязательная регистрация структур модели и отображений. Для того, чтобы всегда все структуры были гарантированно зарегистрированы добавим их в init() функцию.
 Полный код модели:
 В контролере будет примерно такой код:
 Теперь для «ещё одного» изменения json достаточно просто добавить ещё одно представление и не забыть зарегистрировать его в init().