Зарегистрируйтесь, чтобы продолжить обучение

Сериализация данных в JSON Веб-разработка на Go

В вебе взаимодействие происходит по схеме клиент-сервер. Клиент отправляет запрос, а сервер отдает ответ.

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

В этом уроке мы разберем формат сериализации данных — JSON. Мы рассмотрим, как сериализовать и десериализовать данные в JSON в Golang.

Сериализация и десериализация в Golang

Представим, что нам нужно получить список пользователей. Клиент отправляет запрос GET /users, а сервер отдает ответ в виде списка пользователей. Список пользователей — это массив структур. Каждая структура содержит информацию о пользователе. Например, имя, фамилию, возраст, пол.

Мы знаем, что HTTP — это текстовый протокол, поэтому в запросе и ответе могут быть только текстовые данные. Поэтому нам нужно передать массив пользователей в текстовом виде. В этом нам поможет сериализация — процесс преобразования данных в строку. Сервер сериализует массив пользователей в строку и передает клиенту. Клиент может десериализовать строку обратно в массив структур пользователей.

Десериализация — это процесс преобразования сериализованной строки обратно в данные, которое понимает приложение.

На схеме ниже показано, как происходит сериализация и десериализация данных при отправке HTTP-ответа от сервера к клиенту:

set

Сервер сериализует объекты пользователей в строку, отправляет это в теле HTTP-ответа, а клиент читает тело ответа и десериализует строку обратно в данные.

Существует множество протоколов сериализации данных. Они отличаются способами преобразования данных в строку и форматами сериализованной строки. Наиболее популярные протоколы сериализации данных: JSON, XML, YAML, MessagePack и Protocol Buffers.

В этом курсе мы разберем самый популярный протокол сериализации данных — JSON. У него есть следующие преимущества:

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

Перед тем, как продолжить, ознакомьтесь с этим форматом сериализации в документации.

Для сериализации и десериализации данных в JSON используется стандартная библиотека. Для этого в ней есть две функции:

  • json.Marshal() — сериализует данные в JSON
  • json.Unmarshal() — десериализует данные из JSON

Так в GO происходит сериализация и десериализация структуры:

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type User struct {
    ID       int64  `json:"id"`
    Email    string `json:"email"`
    Name     string `json:"name"`
    Age      int    `json:"age,omitempty"`
    password string
}

func main() {
    user := User{
        ID:       222,
        Email:    "john@doe.com",
        Name:     "John",
        Age:      25,
        password: "secret",
    }

    // Сериализация структуры в строку
    jsonBytes, err := json.Marshal(&user)
    if err != nil {
        // Используем Fatal только для примера,
        // нельзя использовать в реальных приложениях
        log.Fatalln("marshal ", err.Error())
    }

    fmt.Printf("JSON string: %s\n", string(jsonBytes))

    // Десериализация строки в структуру
    deserializedUser := User{}
    err = json.Unmarshal(jsonBytes, &deserializedUser)
    if err != nil {
        // Используем Fatal только для примера,
        // нельзя использовать в реальных приложениях
        log.Fatalln("unmarshal ", err.Error())
    }

    fmt.Printf("Deserialized user struct: %+v\n", deserializedUser)
}

При запуске программы видим вывод:

JSON string: {"id":222,"email":"john@doe.com","name":"John","age":25}
Deserialized user struct: {ID:222 Email:john@doe.com Name:John Age:25 password:}

В данном Go-приложении мы преобразовали объект пользователя User в JSON-строку и обратно. В результате мы получили ту же структуру User, но с пустым полем password, потому что указали, что это приватное поле. Если бы мы отправили такую JSON-строку по сети другому веб-приложению, то оно смогло бы восстановить изначальный объект User без поля password.

Важные моменты работы с JSON в Go:

  • В структуре сериализуются только публичные свойства, которые написаны с заглавной буквы. В нашем примере свойство password — приватное, поэтому оно не попало в JSON-строку. Такое же правило действует и на десериализацию. Если мы добавим значение password в JSON-строку, то это значение все равно не попадет в десериализованную структуру
  • В JSON-строке можно указывать название свойства, которое будет отличаться от названия свойства в структуре. Для этого используется тег json:"". В нашем примере мы указали тег для свойства ID в виде json:"id". Так можно указывать, в каком виде должно называться свойство в JSON-строке в зависимости от соглашений в проекте

  • При описании тега json:"" можно указать опцию omitempty. Эта опция говорит о том, что если значение свойства является нулевым, то это поле не нужно включать в сериализованную строку

Мы научились работать с JSON в Golang, теперь рассмотрим, как использовать этот формат в веб-приложениях.

JSON в веб-приложении

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

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

Сервер возвращает ответ с JSON, когда клиент запрашивает данные. Например, клиент запросил список курсов на Хекслете, и сервер вернул JSON массив структур курсов.

Реализуем эти концепции на примере Fiber веб-приложения. В Fiber работа с JSON происходит через контекст запроса c *fiber.Ctx. Для этого есть два метода:

  • c.BodyParser() — преобразует JSON-тело запроса в объект
  • c.JSON() — отправляет JSON-строку в теле ответа

Реализуем веб-приложение для сохранения логов, в котором отправим JSON в теле запроса и получим JSON в теле ответа.

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

package main

import (
    "fmt"
    "github.com/gofiber/fiber/v2"
    "github.com/google/uuid"
    "github.com/sirupsen/logrus"
)

type (
    CreateLogEntryRequest struct {
        Message   string `json:"message"`
        Level     string `json:"level"`
        Timestamp int64  `json:"timestamp"`
    }

    CreateLogEntryResponse struct {
        ID string `json:"id"`
    }

    LogEntry struct {
        ID        string
        Message   string
        Level     string
        Timestamp int64
    }
)

var logs []LogEntry

func main() {
    webApp := fiber.New()

    webApp.Post("/logs", func(c *fiber.Ctx) error {
        var request CreateLogEntryRequest
        if err := c.BodyParser(&request); err != nil {
            return fmt.Errorf("body parser: %w", err)
        }

        logEntry := LogEntry{
            ID:        uuid.New().String(),
            Message:   request.Message,
            Level:     request.Level,
            Timestamp: request.Timestamp,
        }

        // Упрощенное хранение в памяти приложения
        logs = append(logs, logEntry)

        return c.JSON(CreateLogEntryResponse{
            ID: logEntry.ID,
        })
    })

    logrus.Fatal(webApp.Listen(":80"))
}

Запускаем веб-приложение и отправляем запрос на сохранение тестовой записи:

curl --location --request POST 'http://localhost/logs' \
--header 'Content-Type: application/json' \
--data-raw '{"message": "test", "level": "info", "timestamp": 1663251297}'

В ответ получаем идентификатор:

HTTP/1.1 200 OK

{"id":"8a53055f-6fcc-435a-b2be-35abc978a9ed"}

В данном веб-приложении мы описали структуры запроса и ответа с публичными свойствами и JSON-тегами. Благодаря этому, мы смогли использовать структуру запроса CreateLogEntryRequest в методе c.BodyParser(), чтобы десериализовать тела запроса из JSON. Также мы использовали структуру ответа CreateLogEntryResponse в методе c.JSON(), чтобы сериализовать тела ответа в JSON.

Выводы

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

Еще мы разобрали самый популярный формат сериализации данных — JSON, так как он простой в генерации и чтении по сравнению с аналогами.

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

  • В Go у структур сериализуются только публичные поля
  • В фреймворке Fiber есть функция c.BodyParser(), которая позволяет десериализовать JSON тело запроса в структуру
  • В фреймворке Fiber есть функция c.JSON(), которая позволяет сериализовать структуру тела ответа в JSON

Дополнительные материалы

  1. JSON
  2. JSON and Go
  3. Fiber JSON Response
  4. Fiber Body Parser

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

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

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»