Golang прочитать файл json

Обновлено: 06.07.2024

JSON - один из самых популярных форматов сериализации. Он удобен для чтения, достаточно лаконичен и может быть легко проанализирован любым веб-приложением, использующим JavaScript. Go как современный язык программирования имеет первоклассную поддержку сериализации JSON в своей стандартной библиотеке.

Но есть и укромные уголки. В этом руководстве вы узнаете, как эффективно сериализовать и десериализовать произвольные, а также структурированные данные в/из JSON. Вы также узнаете, как работать с расширенными сценариями, такими как перечисления сериализации.

Пакет JSON

Go поддерживает несколько форматов сериализации в пакете кодирования своей стандартной библиотеки. Одним из них является популярный формат JSON. Вы сериализуете значения Golang, используя функцию Marshal(), в кусочек байтов. Вы десериализуете часть байтов в значение Golang, используя функцию Unmarshal(). Это так просто. Следующие термины эквивалентны в контексте этой статьи:

  • Сериализация/Кодирование/Маршалинг
  • Десериализация/Декодирование/Демаршаллизация

Я предпочитаю сериализацию, потому что она отражает тот факт, что вы преобразуете потенциально иерархическую структуру данных в/из потока байтов.

Marshal

Функция Marshal() может принимать что угодно, что в Go означает пустой интерфейс и возвращает часть байтов и ошибку. Вот ее сигнатура:

func Marshal(v interface<>) ([]byte, error)

Если Marshal() не сможет сериализовать входное значение, она вернет ненулевую ошибку. У Marshal() есть некоторые строгие ограничения (позже мы увидим, как их преодолеть с помощью пользовательских маршаллеров):

  • Ключи карты должны быть строками.
  • Значения карты должны иметь типы, сериализуемые пакетом json.
  • Следующие типы не поддерживаются: Channel, complex и function.
  • Циклические структуры данных не поддерживаются.
  • Указатели будут закодированы (и позже декодированы) как значения, на которые они указывают (или «null», если указатель равен нулю).

Unmarshal

Функция Unmarshal() принимает фрагмент байта, который, как мы надеемся, представляет действительный JSON, и целевой интерфейс, который обычно является указателем на структуру или базовый тип. Он десериализует JSON в интерфейс универсальным способом. Если сериализация не удалась, он вернет ошибку. Вот сигнатура:

func Unmarshal(data []byte, v interface<>) error

Сериализация простых типов

Вы можете легко сериализовать простые типы, например, используя пакет json. Результатом будет не полноценный объект JSON, а простая строка. Здесь int 5 сериализуется в байтовый массив [53], который соответствует строке «5».

Если вы попытаетесь сериализовать неподдерживаемые типы, такие как функция, вы получите ошибку:

Сериализация произвольных данных с помощью карт

Сила JSON в том, что он может очень хорошо представлять произвольные иерархические данные. Пакет JSON поддерживает его и использует общий пустой интерфейс (interface <>) для представления любой иерархии JSON. Вот пример десериализации и последующей сериализации двоичного дерева, где каждый узел имеет значение int и две ветви, левую и правую, которые могут содержать другой узел или быть нулевыми.

JSON null эквивалентен Go nil. Как видно из выходных данных, функция json.Unmarshal() успешно преобразовала большой двоичный объект JSON в структуру данных Go, состоящую из вложенной карты интерфейсов, и сохранила тип значения как int. Функция json.Marshal() успешно сериализовала полученный вложенный объект в то же представление JSON.

Чтобы пройти общие карты интерфейсов, вам нужно использовать утверждения типа. Например:

Сериализация структурированных данных

Работа со структурированными данными часто является лучшим выбором. Go предоставляет отличную поддержку для сериализации JSON в/из структур через его теги struct . Давайте создадим struct , которая соответствует нашему дереву JSON и более умной функции Dump() , которая ее печатает:

Это здорово и намного чище, чем произвольный подход JSON. Но работает ли это? На самом деле, нет. Там нет ошибки, но наш объект дерева не заполняется JSON.

Проблема в том, что поля дерева являются приватными. Сериализация JSON работает только для открытых полей. Таким образом, мы можем сделать поля структуры общедоступными. Пакет json достаточно умен, чтобы прозрачно преобразовать строчные буквы «value», «left» и «right» в соответствующие им имена полей верхнего регистра.

Пакет json будет автоматически игнорировать не отображенные поля в JSON, а также приватные поля в вашей struct . Но иногда вам может потребоваться отобразить определенные ключи в JSON на поле с другим именем в вашей struct . Вы можете использовать теги struct для этого. Например, предположим, что мы добавили еще одно поле с именем «label» в JSON, но нам нужно сопоставить его с полем «Tag» в нашей структуре.

Вот новый JSON с корневым узлом дерева, помеченным как «root», правильно сериализованный в поле Tag и напечатанный в выходных данных:

Написание обычного упаковщика

Вы часто захотите сериализовать объекты, которые не соответствуют строгим требованиям функции Marshal(). Например, вы можете захотеть сериализовать карту с помощью ключей int. В этих случаях вы можете написать собственный упаковщик/распаковщик, реализовав интерфейсы Marshaler и Unmarshaler .

Примечание о правописании. В Go принято называть интерфейс одним методом, добавляя суффикс «er» к имени метода. Таким образом, несмотря на то, что более распространенным написанием является «Marshaller» (с двойным L), имя интерфейса - просто «Marshaler» (один L).

Вот интерфейсы Marshaler и Unmarshaler:

Вы должны создать тип при выполнении пользовательской сериализации, даже если вы хотите сериализовать встроенный тип или композицию встроенных типов, таких как map[int]string . Здесь я определяю тип с именем IntStringMap и реализую интерфейсы Marshaler и Unmarshaler для этого типа.

Метод MarshalJSON() создает map[string]string , преобразует каждый из своих собственных ключей int в строку и сериализует карту со строковыми ключами, используя стандартную функцию json.Marshal() .

Метод UnmarshalJSON() делает прямо противоположное. Он десериализует массив байтов данных в map[string]string , а затем преобразует каждый строковый ключ в int и заполняет сам себя.

Вот как это использовать в программе:

Сериализация Enums

Перечисления Go могут быть довольно неприятными для сериализации. Идея написать статью о сериализации Go json возникла из-за вопроса, который мне спросил коллега о том, как сериализовать перечисления. Вот enum Go. Константы Zero и One равны целым числам 0 и 1.

Хотя вы можете думать, что это int, и во многих отношениях это так, вы не можете сериализовать его напрямую. Вы должны написать собственный маршалер/демаршалер. Это не проблема после последнего раздела. Следующие MarshalJSON() и UnmarshalJSON() будут сериализовать/десериализовать константы ZERO и ONE в/из соответствующих строк «Zero» и «One».

Давайте попробуем встроить этот EnumType в struct и сериализовать его. Основная функция создает EnumContainer и инициализирует его с именем «Uno» и значением нашей константы enum ONE , которая равна int 1.

Ожидаемый результат - «Uno: 1», но вместо этого «Uno: 0». Что случилось? В коде упаковщика/распаковщика нет ошибки. Оказывается, вы не можете встраивать перечисления по значению, если хотите их сериализовать. Вы должны вставить указатель на перечисление. Вот модифицированная версия, где она работает как положено:

Заключение

Go предоставляет много опций для сериализации и десериализации JSON. Важно понять все входы и выходы пакета encoding/json, чтобы воспользоваться преимуществами.

В этом уроке вы получите всю мощь, в том числе и сериализацию неуловимых перечислений Go.

В данном руководстве будет показано, как работать с JSON в Golang.

Содержание статьи

JSON в Golang

JSON (JavaScript Object Notation) является форматом обмена данными. Он не только легко читается и записывается людьми, но и без проблем анализируется и генерируется машинами. application/json является официальным Интернет медиа типом для JSON. Названием файлового расширения JSON является .json .


Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Пакет encoding/json имплементирует кодирование и декодирование JSON.

Кодирование в формат JSON в Golang

Функция Marshal кодирует данные в формат JSON.

В данном примере мы трансформируем структуру и срез структуры в формат JSON.

Мы объявляем структуру User .

Создаем экземпляр структуры.

Кодируем структуру u1 в JSON с помощью функции Marshal .

Так как json_data является байтовым массивом, мы конвертируем его в строку с помощью строковой функции.

У нас есть срез users .

Кодируем срез users через функцию Marshal.

Выводим закодированный срез.

Таков результат вывода.

Декодирование данных из JSON в Golang

Функция Unmarshal декодирует данные формата JSON в Go структуру.

Мы декодируем JSON в структуру и срез структур Go.

У нас есть объект JSON, который преобразуется в байты.

Декодируем структуру u1 из формата JSON в структуру через функцию Unmarshal .

Декларируем срез структуры User .

Данный массив JSON будет декодирован в срез.

Мы декодируем JSON-массив в срез через функцию Unmarshal .

Выводим декодированный JSON массив, пользователь за пользователем.

Таков результат вывода.

Аккуратный вывод формата JSON в Golang

Аккуратно вывести данные JSON можно с помощью функции MarshalIndent .

В данном примере у нас есть карта звуков, которые произносят разные птицы. Результат вывода приводится в более красивый вид с помощью функции MarshalIndent .

Мы видим вывод с отступами, который читается намного удобнее.

Открываем JSON файл в Golang

В следующем примере, JSON будет открываться из файла.

Сохраните вышеуказанные данные в файле data.json .

Данный пример читает JSON из файла и декодирует их в срез структуры с пользователями.

Таков результат вывода.

На данный момент на Космической Станции находятся три астронавта.

В данном руководстве мы рассмотрели работу с JSON в Golang.


Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.

Один из наиболее распространенных видов данных, хранящихся в конфигурационных файлах, - это опции. В этой статье я расскажу о некоторых нюансах, которые необходимо учитывать при сохранении опций в JSON и их анмаршалинге в Go.

В частности, наиболее важное различие между опциями и любыми другими данными заключается в том, что опции зачастую, извините за каламбур… опциональны. Наша программа может иметь большое количество всевозможных параметров конфигурации (опций), но нам может понадобиться инициировать какую-либо конкретную инвокацию только с ограниченным их подмножеством, оставив для всего остального значения по умолчанию.

Основы - частичный анмаршалинг, omitempty и неизвестные поля

Начнем с основ. Рассмотрим следующую структуру, которая представляет из себя опции произвольной программы:

Эта структура содержит всего 4 опции, но в реальных программах их могут быть десятки.

Предположим, мы хотим указать эти опции в JSON-файле конфигурации. Полный список опций может выглядеть примерно так:

Если в ваших файлах конфигурации всегда указаны все опции, то говорить дальше особо не о чем. Просто вызовите json.Unmarshal , и дело с концом.

На практике же достаточно редко все бывает так просто. Нам следует обработать сразу несколько особых случаев:

В JSON-конфиге могут отсутствовать некоторые поля, и мы хотим, чтобы наша структура в Go имела для них значения по умолчанию.

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

В случае (1) пакет json Go будет присваивать значения только полям, найденным в JSON; другие поля просто сохранят свои нулевые значения Go. Например, если бы в JSON вообще не было поля level , в анмаршаленной структуре Options Level будет равен 0. Если такое поведение нежелательно для вашей программы, переходите к следующему разделу.

В случае (2) пакет json по умолчанию поведет себя вполне терпимо и просто проигнорирует неизвестные поля. То есть предположим, что входной JSON:

json.Unmarshal без проблем распарсит это в Options , установив Id в значение "foobar" , Level и Power в 0, а Verbose в false . Он проигнорирует поле bug .

В одних случаях такое поведение является желательным, в других - нет. К счастью, пакет json позволяет настроить это, предоставляя явную опцию для JSON-декодера в виде DisallowUnknownFields :

Теперь парсинг вышеупомянутого фрагмента JSON приведет к ошибке.

Наконец, вы можете заметить, что наша структура Options имеет тег omitempty , указанный для всех полей. Это означает, что поля с нулевыми значениями не будут внесены в JSON. Например:

Потому что все остальные поля имеют нулевые значения. Если вы напротив хотите всегда вносить все поля, не указывайте omitempty.

Установка значений по умолчанию

В приведенном выше примере мы видели, что отсутствующие в JSON-представлении поля будут преобразованы в нулевые значения Go. Это нормально, если значения ваших параметров по умолчанию также являются их нулевыми значениями, что не всегда так. Что, если значение по умолчанию Power должно быть 10, а не 0? То есть, когда JSON не имеет поля «power», вы хотите установить Power равным 10, но вместо этого Unmarshal устанавливает его в ноль.

Вы можете подумать - это же элементарно! Я буду устанавливать Power в его значение умолчанию 10 всякий раз, когда он маршалится из JSON как 0! Но подождите. Что произойдет, если в JSON указано значение 0?

На самом деле, эта проблема решается наоборот. Мы установим значения по умолчанию сначала, а затем позволим json.Unmarshal перезаписать поля по мере необходимости:

Теперь вместо прямого вызова json.Unmarshal на Options , нам придется вызывать parseOptions .

В качестве альтернативы мы можем хитро спрятать эту логику в пользовательском методе UnmarshalJSON для Options :

В этом методе любой вызов json.Unmarshal для типа Options будет заполнять значение по умолчанию Power правильно. Обратите внимание на использование псевдонимного типа options - это нужно для предотвращения бесконечной рекурсии в UnmarshalJSON .

Этот подход прост и понятен, но у него есть некоторые недостатки. Во-первых, он прочно связывает значения полей по умолчанию с логикой парсинга. Вполне возможно, что мы хотим, чтобы пользовательский код устанавливал значения по умолчанию позже; прямо сейчас значения по умолчанию нужно установить перед анмаршалингом.

Второй недостаток заключается в том, что это работает только для простых случаев. Если наша структура Options содержит срез или мапу других структур, мы не сможем проставить значения по умолчанию таким образом. Примите во внимание:

Если мы хотим заполнить значения по умолчанию для Power каждой Region , мы не сможем сделать это на уровне Options . Мы должны написать собственный метод анмаршалинга для Region. Это сложно масштабировать для произвольно вложенных структур - распространение нашей логики значений по умолчанию на несколько методов UnmarshalJSON не оптимально.

Альтернативой является использование совершенно другого подхода, перекладывая логику значений по умолчанию на пользователя. Мы можем добиться этого с помощью полей-указателей.

Значения по умолчанию и поля-указатели

Мы можем определить нашу структуру Options как:

Это очень напоминает исходное определение, за исключением того, что все поля теперь являются указателями. Предположим, у нас есть следующий текст JSON:

Обратите внимание, что указаны все поля, кроме "power". Мы можем анмаршалить это как обычно:

Но теперь мы можем четко различить поля, которые не были указаны вообще (они будут анмаршалены в nil указатель), и поля, которые были указаны с нулевыми значениями (они будут анмаршалены в валидные указатели на значения с нулевыми значениями). Например, мы можем написать следующую обертку парсера для анмаршалинга Options и установки значений по умолчанию по мере необходимости:\

Обратите внимание, как мы устанавливаем opts.Power ; это одно из неудобств работы с указателями, потому что в Go нет синтаксиса, позволяющего принимать адреса литералов встроенных типов, таких как int . Однако это не слишком большая проблема, поскольку существуют простые вспомогательные функции, которые могут сделать нашу жизнь чуть более приятной:

Имея это под рукой, мы могли бы просто написать opts.Power = Int(10) .

Самая полезная особенность этого подхода заключается в том, что он не заставляет нас назначать значения по умолчанию в месте, где парсится JSON. Мы можем передать Options в пользовательский код, и пусть уже он разбирается со значениями по умолчанию, когда ему встречаются поля с nil .

Так являются ли указатели волшебным решением нашей проблемы - «отличить неопределенные значения от нулевых значений»? Вроде того. Указатели, безусловно, являются жизнеспособным решением, которое должно хорошо работать. Официальный пакет Protobuf использует тот же подход для protobuf-ов proto2 , проводящих различие между необходимыми и опциональными полями. Так что этот метод прошел проверку боем!

Тем не менее, это решение не идеально. Прежде всего, несмотря на то, что Go большую часть времени действительно хорошо скрывает дополнительную синтаксическую нагрузку при работе с указателями, в некоторых случаях немного возни все же просачивается (например, получение адреса встроенного литерала, как показано выше). Еще одна потенциальная проблема - производительность. Указатели часто означают динамическое выделение памяти и могут вызывать проблемы с производительностью в некоторых сценариях, хотя, если говорить о структурах параметров, это вряд ли станет проблемой.

poster

Решил в свободное время изучить новый(для себя) язык программирования Golang или Go. Начал изучения с сайта Stepik, на котором имеется отличный бесплатный курс он состоит из теории и практики, ниже я представляю свой конспект этого курса, помимо этого сохраняю решения которые мне понравились, по мере изучения языка некоторые решения могут быть изменены.

Перебор числа любого разряда с конца

Замена переменных местами

Например нам надо чтобы переменная А стала равна переменной Б, а Б в свою очередь стал равный А с прошлым значением до равенства с Б.

Формат вывода через Printf

Через функцию Printf можно выводить данные в различном виде с помощью спецификаторов, вот некоторые из часто применяемых:

  • %q - для вывода символов в одинарных кавычках
  • %в - для вывода чисел в десятичной системе
  • %t - для вывода значения типа boolean
  • %T - для вывода типа переменной
  • %f - для чисел с плавающей точкой (%9.2F) Где 9 ширина, 2 точность этим параметром можно пренебречь
  • %v - универсальный спецификатор (для строк, чисел с плавающей точкой, целочисленных и boolean)

Функция с Return, который возвращает

Особенность языка, которая позволяет автоматически возвращать все указанные для возврата переменные без указания их в return

Работа с env

У каждого проекта могут быть свои настройки, также проект может быть расположен на тестовой машине и нам необходимо будет указать собственные пути к компилятору для этого воспользуемся консолью

Нюансы работы с командной строкой

При выполнении команд в через командную строку не обязательно передавать путь в виде аргумента если мы находимся в нужной папке

Установка внешних модулей

Указатели

Простой пример для понимания для чего нужны указатели

& — это “адрес этого блока памяти”.

Указатели

В первом случае мы копируем все эти блоки памяти — и, в реальности, их может быть запросто больше, чем 2, хоть 2 миллиона блоков, и они все будут копироваться, а это одна из самых дорогостоящих операций. Во втором же случае, мы копируем лишь один блок памяти — в котором хранится адрес в памяти

  • “Амперсанд” & звучит похоже на “Адрес” (ну, и то слово и другое на “А” начинается, по крайней мере), поэтому &x читается как “адрес переменной X”
  • звёздочка * ни на что не похоже на звучит, но может использоваться в двух случаях — перед типом ( var x *MyType — означает “тип указателя на MyType”) и перед переменной ( *x = y — означает “значение по адресу”)

Структуры

Структуры это что-то типо классов или ассоциативных массивов в других языках

Пример создания метода структуры

Структуры также могут быть вложены друг в друга. И доступ к методам дочерних структур имеет родитель в которого они вложены

Строки

Go содержит различные функции для работы со строками в пакете string. Вот некоторые из них

Важно помнить что функция len() возвращает количество байт а не кол-во символов строки, для получения количества символов следует использовать библиотеку utf8 и функцию RuneCounInString()

Считать строку с пробелами

В целом работа со строками достаточно сложная в отличии от того же php, к примеру чтобы проверить строку соответствует ли она условиям (1 символ с большой буквы, последний символ с точкой) я написал полотно, но нашлось решение покороче

Преобразование типов

числа в строку

строка в число

Функции

Стоит помнить, что язык Golang позволяет пропускать возвращаемые значения

оператор defer

Он позволяет выполнить определенную операцию даже если сработает panic (он прекращает работу программы ), если будет объявлено несколько операторов defer то они сработают в последовательности LIFO (Last-In, First-Out) т.е тот кто был определен первым сработает ПОСЛЕДНИМ. Также стоит помнить что значения переменных которые использует данный оператора сохраняются на момент его определения и они могут быть неактуальны

Отображения (Map)

На первых взгляд это что-то типо ассоциативных массивов, пример вызова

Проверка на наличие ключа в map

Важно помнить, что если перебирать map используя range то, каждый раз результат последовательности вывода данных будет разным, поэтому чтобы контролировать выдачу надо использовать массив ключей мап и отдельного его перебирать

Интерфейсы

Интерфейсы есть во многих языках, в го интерфейсы указывают на необходимость описания всех методов которые необходимы какому либо объекту

Приведение типа

Работа с файлами

Для работы с файлами рекомендуется использовать библиотеку os

Работа с json

Для работы с данным форматом есть библиотека json, которая имеет кодировать Marshal и декодировать Unmarshal файл. Для декодирования и кодирования необходимо создать тип данных структуру которая будет (частично) соответствовать в случае с декодированием и полностью если кодировать файлу json. Ниже рассмотрим пример декодирования

Для того чтобы самому не создавать нужную структуру, можно воспользоваться удобным инструментом

Дата и время

Но лучше рассмотреть библиотеку fmtdate

Параллелизм

в языке го используется горотина для создания параллельного выполнения кода

Горутина

Каналы

Канал — это объект связи, с помощью которого горутины обмениваются данными. Технически это конвейер (или труба), откуда можно считывать или помещать данные.

В go каналы являются указателями.

Канал имеет две основные операции:

  1. Отправление (запись) - передает через канал значения из одной горутины в другую
  2. Получение (чтение) - получение через канал значения из другой горутины

Чтение из канала будет осуществляться пока канал открыт.

Помимо типов данных канал имеет длину и ёмкость (их можно получить через len() и cap():

  • длинна - количество значений в канале в текущий момент
  • емкость - размер буфера

Если при создании канала указывается ёмкость = 0 или не указывается совсем, то такой канал считается не буферизированным.

Выбор между буферизованным и небуферизованным каналом, как и выбор емкости каналов, может влиять на корректность работы программы в целом. Небуферизованные каналы дают более надежные гарантии синхронизации, потому что каждая операция отправления связана с операцией получения. В случае буферизованных каналов, эти операции разделены.

Deadlock (Взаимная блокировка)

Чтение или запись данных в канал блокирует горутину и контроль передается свободной горутине. Если такие горутины отсутствуют либо они все “спят” в этот момент может возникнут deadlock который приведет к завершению программы.

Если вы попытаетесь считать данные из канала, но в канале будут отсутствовать данные, планировщик заблокирует текущую горутину и разблокирует другую в надежде, что какая-либо горутина передаст данные в канал. То же самое произойдет в случае отправки данных: планировщик заблокирует передающую горутину, пока другая не считает данные из канала.

Горутина не блокируется до тех пор пока буфер не будет заполнен, но при чтении из канала операция не будет завершена пока не опустошит весь буфер!

Однонаправленные каналы

Можно создать канал который будет только передавать или только принимать данные

Читайте также: