Введение в JSON
JSON (JavaScript Object Notation) – это легкий формат обмена данными, основанный на синтаксисе объектов JavaScript. Он широко используется для передачи информации между клиентом и сервером в сетевых приложениях.
JSON представляет данные в виде пар «ключ-значение» и может содержать массивы, числа, строки, логические значения и null. Он обеспечивает простоту чтения и записи для людей, а также легкость разбора и генерации для компьютеров.
Чтение JSON
Десериализация (чтение JSON) – это процесс преобразования данных из формата JSON в объекты Go. Для этих целей используется пакет encoding/json
, который входит в стандартную библиотеку языка. Основная функция для чтения Unmarshal
считывает JSON-данные и сохраняет результат в значении, на которое указывает заданная переменная. Если же она представляет собой nil или не является указателем, то функция вернет InvalidUnmarshalError
.
Общая сигнатура: func Unmarshal(data []byte, v any) error
Рассмотрим применение функции Unmarshal
на конкретном примере:
type Worker struct { Name string `json:"name"` Age int `json:"age"` Job string `json:"job"` } func main() { var worker Worker jsonData := `{"name":"Петя", "age":18, "job":"Backend-разработчик"}` err := json.Unmarshal([]byte(jsonData), &worker) if err != nil { fmt.Println("Ошибка чтения JSON-данных:", err) } fmt.Println(worker) }
В результате работы программы будет выведено «{Петя 18 программист}».
Функция Unmarshal
обладает интересной особенностью – она считывает только поля, соответствующие объявленным типам. Такое поведение используется для выборки определенных данных из большого JSON-файла.
В следующем примере будут заполнены только поля Name
и Age
, а Job
останется проигнорированным. Вывод будет следующий: «{Витя 20 }».
type Worker struct { Name string `json:"name"` Age int `json:"age"` Job string `json:"job"` } ... var worker Worker jsonData := `{"name":"Витя", "age": 20, "city":"Москва"}` err := json.Unmarshal([]byte(jsonData), &worker)
?? Библиотека Go разработчика Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека Go разработчика» ? Библиотека задач по Go Интересные задачи по Go для практики можно найти на нашем телеграм-канале «Библиотека задач по Go» ? Библиотека Go для собеса Подтянуть свои знания по Go вы можете на нашем телеграм-канале «Библиотека Go для собеса»
Запись JSON
Сериализация (запись JSON) – это процесс преобразования объекта в формат, который можно сохранить или передать по сети. Для этого в Go используется функция Marshal()
из ранее рассмотренного пакета encoding/json
. Она возвращает два значения – срез байт и ошибку.
Её сигнатура имеет следующий вид: func Marshal(v any) ([]byte, error)
type Worker struct { Name string `json:"name"` Age int `json:"age"` Job string `json:"job"` } func main() { workerInfo := Worker{Name: "Ваня", Age: 14, Job: "Go-разработчик"} jsonInfo, err := json.Marshal(workerInfo) if err != nil { fmt.Println("Ошибка записи данных:", err) } fmt.Println(jsonInfo) }
Стоит учитывать, что записаны могут быть только структуры данных, представимые в виде корректного формата JSON.
Для кодировки типа map он должен иметь вид map[string]T
, где T – любой тип, поддерживаемый пакетом encoding/json
.
Циклические структуры данных могут привести к попаданию функции Marshal
в бесконечный цикл, поэтому не поддерживаются для преобразования.
Для кодирования указателей необходимо представить их в виде значений, на которые они указывают. В случае nil-указателей это будет nil.
Функции, каналы и тип complex не поддерживаются.
Потоковые кодировщики и декодеры
Интерфейсы Reader и Writer
В Go существуют два основных интерфейса из пакета io
стандартной библиотеки, io.Reader
и io.Writer
. Они широко используются для ввода/вывода данных, при работе с файлами, сетевыми соединениями и другими объектами, поддерживающими операции чтения и записи.
Интерфейс io.Reader
определяет метод Read
, который принимает в качестве параметра буфер для чтения, а возвращает количество байт, прочитанных из источника, и ошибку. При завершении потока данных io.Reader
возвращает ошибку io.EOF
(конец файла).
type Reader interface { Read(p []byte) (n int, err error) }
Интерфейс io.Writer
определяет метод Write
, который принимает в качестве параметра буфер для записи данных, а возвращает количество байт, записанных в целевой объект, и ошибку.
type Writer interface { Write(p []byte) (n int, err error) }
При желании каждый разработчик может написать собственные интерфейсы Reader
и Writer
, руководствуясь соглашениями языка.
Кодировщик json.Encoder
Структура json.Encoder
предназначена для кодирования JSON-данных и их последующей записи в выходной поток Writer
.
Так выглядит сигнатура функции для создания кодировщика: func NewEncoder(w io.Writer) *Encoder
type Shape struct { Type string `json:"name"` Width int `json:"width"` Height int `json:"height"` } func main() { // Создание слайса структур Workers с необходимыми данными shapes := []Shape{ {Type: "Квадрат", Width: 10, Height: 10}, {Type: "Прямоугольник", Width: 50, Height: 20}, } var buf bytes.Buffer encoder := json.NewEncoder(&buf) // Запись JSON-данных в буфер if err := encoder.Encode(shapes); err != nil { fmt.Println("Ошибка при записи JSON-данных:", err) return } fmt.Println(buf.String()) }
Декодер json.Decoder
Структура json.Decoder
позволяет декодировать данные JSON из интерфейса Reader
. К примеру, из файла, буфера или сетевого соединения. Decoder
также обеспечивает возможность контроля синтаксических ошибок и обработки потока JSON-данных в режиме реального времени.
Сигнатура для создания декодера: func NewDecoder(r io.Reader) *Decoder
Рассмотрим пример использования этой функции.
type Student struct { Name string `json:"name"` Grade int `json:"grade"` } func main() { jsonData := `{"name":"Иван", "grade":10}` // Создание буфера с данными в формате JSON reader := strings.NewReader(jsonData) // Создание Decoder для чтения из буфера decoder := json.NewDecoder(reader) var student Student // Чтение JSON из буфера и их запись в student if err := decoder.Decode(&student); err != nil { fmt.Println("Ошибка декодирования:", err) return } fmt.Println(student.Name, student.Grade) }
На экран будет выведено: Иван 10
Тип NullString
В Go есть специальный тип NullString
для работы с пустыми или отсутствующими значениями JSON-данных. Он позволяет явно указать, является ли строка пустой или нулевой.
Стандартная библиотека Go не предоставляет встроенного типа NullString
, но можно создать его самостоятельно, используя структуру или указатель на строку.
type NullString struct { String string Valid bool }
Тип NullString
также бывает полезен для десериализации JSON-данных в нестандартном формате. С его помощью можно создавать собственные функции наподобие Unmarshal
.
В качестве примера создадим кастомный десериализатор с использованием NullString
.
type NullString struct { String string Valid bool } func (nstr *NullString) UnmarshalCustomData(jsonData []byte) error { // Проверяем, являются ли JSON-данные нулевой строкой if string(jsonData) == "null" { nstr.Valid = false return nil } var s string // Десериализация JSON if err := json.Unmarshal(jsonData, &s); err != nil { return err } // Присвоение строки типу NullString nstr.String = s nstr.Valid = true return nil }
Декодирование неизвестных данных
Зачастую в JSON хранятся данные разных типов, которые необходимо правильно обработать. В этом случае может помочь декодирование значений в интерфейс и их последующий перебор с использованием switch-case. Такой подход позволяет сохранить преимущества безопасности типов.
Для детального понимания рассмотрим конкретный пример.
func main() { jsonData := []byte(`{"name":"Ваня","grade":11,"classmates":["Петя", "Игорь","Глеб"]}`) // Десериализация JSON-данных var data interface{} if err := json.Unmarshal(jsonData, &data); err != nil { fmt.Println("Ошибка при чтении JSON:", err) } // Type assertion res := data.(map[string]interface{}) // Перебор ключей и значений for key, val := range res { switch value := val.(type) { case string: fmt.Println(key, "имеет тип string - ", value) case float64: fmt.Println(key, "имеет тип float64 - ", value) case []interface{}: fmt.Println(key, "имеет тип []interface{} - ", value) default: fmt.Println("Значение элемента неизвестно") } } }
Вывод будет следующий:
name имеет тип string - Ваня grade имеет тип float64 - 11 classmates имеет тип []interface{} - [Петя Игорь Глеб]
Материалы по теме
ТОП-11 бесплатных учебных курсов по Go
ТОП-9 книг по языку программирования Go в 2023 году: от новичка до профессионала