
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на конкретном примере:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 }».
|
1 2 3 4 5 6 7 8 9 |
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) |
Запись JSON
Сериализация (запись JSON) – это процесс преобразования объекта в формат, который можно сохранить или передать по сети. Для этого в Go используется функция Marshal() из ранее рассмотренного пакета encoding/json. Она возвращает два значения – срез байт и ошибку.
Её сигнатура имеет следующий вид: func Marshal(v any) ([]byte, error)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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 (конец файла).
|
1 2 3 |
type Reader interface { Read(p []byte) (n int, err error) } |
Интерфейс io.Writer определяет метод Write, который принимает в качестве параметра буфер для записи данных, а возвращает количество байт, записанных в целевой объект, и ошибку.
|
1 2 3 |
type Writer interface { Write(p []byte) (n int, err error) } |
При желании каждый разработчик может написать собственные интерфейсы Reader и Writer, руководствуясь соглашениями языка.
Кодировщик json.Encoder
Структура json.Encoder предназначена для кодирования JSON-данных и их последующей записи в выходной поток Writer.
Так выглядит сигнатура функции для создания кодировщика: func NewEncoder(w io.Writer) *Encoder
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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
Рассмотрим пример использования этой функции.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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, но можно создать его самостоятельно, используя структуру или указатель на строку.
|
1 2 3 4 |
type NullString struct { String string Valid bool } |
Тип NullString также бывает полезен для десериализации JSON-данных в нестандартном формате. С его помощью можно создавать собственные функции наподобие Unmarshal.
В качестве примера создадим кастомный десериализатор с использованием NullString.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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. Такой подход позволяет сохранить преимущества безопасности типов.
Для детального понимания рассмотрим конкретный пример.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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("Значение элемента неизвестно") } } } |
Вывод будет следующий:
|
1 2 3 |
name имеет тип string - Ваня grade имеет тип float64 - 11 classmates имеет тип []interface{} - [Петя Игорь Глеб] |