Avatar ·

Как красиво вывести JSON в Go: удобное форматирование для отладки и логов

Всем привет! Я только начал изучать Go и столкнулся с проблемой при работе с JSON. У меня есть структура, которую я сериализую в JSON, но когда я вывожу результат через fmt.Println, всё отображается одной строкой без форматирования. Это очень неудобно для отладки, особенно когда структура большая и вложенная. Вот пример моего кода: ```go package main import ( "encoding/json" "fmt" ) type User struct { Name string `json:"name"` Age int `json:"age"` Hobbies []string `json:"hobbies"` Address struct { City string `json:"city"` Zip string `json:"zip"` } `json:"address"` } func main() { u := User{ Name: "Alice", Age: 30, Hobbies: []string{"reading", "coding", "hiking"}, } u.Address.City = "Moscow" u.Address.Zip = "101000" data, _ := json.Marshal(u) fmt.Println(string(data)) // Вывод: {"name":"Alice","age":30,"hobbies":["reading","coding","hiking"],"address":{"city":"Moscow","zip":"101000"}} } ``` Как видите, весь JSON выводится в одну строку. Если у меня будет много полей или вложенные объекты, читать такой вывод практически невозможно. Я пробовал использовать `json.MarshalIndent`, но не уверен, что это правильно. Подскажите, как сделать красивый pretty-print JSON в Go? Хочется, чтобы каждый уровень вложенности имел отступы, и было удобно анализировать данные в консоли или логах. Заранее спасибо! <<>>

Используйте json.MarshalIndent для форматированного вывода JSON

Вы уже на правильном пути! В стандартной библиотеке Go есть функция json.MarshalIndent, которая делает именно то, что вам нужно. Она принимает три аргумента: данные для сериализации, префикс (обычно пустая строка) и отступ (например, пробелы или табуляция). Вот как исправить ваш код:

package main

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

type User struct {
    Name    string   `json:"name"`
    Age     int      `json:"age"`
    Hobbies []string `json:"hobbies"`
    Address struct {
        City  string `json:"city"`
        Zip   string `json:"zip"`
    } `json:"address"`
}

func main() {
    u := User{
        Name:    "Alice",
        Age:     30,
        Hobbies: []string{"reading", "coding", "hiking"},
    }
    u.Address.City = "Moscow"
    u.Address.Zip = "101000"

    // Используем json.MarshalIndent вместо json.Marshal
    data, err := json.MarshalIndent(u, "", "  ") // два пробела для отступа
    if err != nil {
        log.Fatalf("Ошибка сериализации: %v", err)
    }
    fmt.Println(string(data))
}

Теперь вывод будет выглядеть так:

{
  "name": "Alice",
  "age": 30,
  "hobbies": [
    "reading",
    "coding",
    "hiking"
  ],
  "address": {
    "city": "Moscow",
    "zip": "101000"
  }
}

Как это работает?

  • Первый аргумент — данные, которые нужно сериализовать (ваша структура, map или срез).
  • Второй аргумент — префикс. Обычно пустая строка "", но можно задать, например, ">> " для отладки.
  • Третий аргумент — отступ. Чаще всего используют " " (два пробела) или "\t" (табуляция).

Дополнительные советы для удобной отладки

Если вы часто выводите JSON в консоль или логи, рекомендую создать вспомогательную функцию:

func prettyPrint(v interface{}) {
    data, err := json.MarshalIndent(v, "", "  ")
    if err != nil {
        log.Printf("Ошибка pretty-print: %v", err)
        return
    }
    fmt.Println(string(data))
}

// Использование:
prettyPrint(u)

Также можно выводить JSON напрямую в os.Stdout или логгер:

encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", "  ")
encoder.Encode(u)

Этот способ особенно полезен, если вы работаете с потоками данных, например, в HTTP-ответах.

Почему json.Marshal не подходит?

Функция json.Marshal создаёт компактный JSON без лишних пробелов и переводов строк. Это оптимально для передачи по сети или записи в файл, но не для чтения человеком. json.MarshalIndent же добавляет форматирование, что делает вывод читаемым.

Не забывайте обрабатывать ошибки — если структура содержит несериализуемые поля (например, каналы или функции), json.MarshalIndent вернёт ошибку. Используйте log.Fatalf или возвращайте ошибку в реальном коде.

Теперь ваш JSON будет красиво отформатирован, и вы легко сможете анализировать даже сложные вложенные структуры!

<<>>

Использование json.Encoder с настройкой отступов

Альтернативный подход — использовать json.Encoder, который позволяет гибко управлять выводом. Это особенно удобно, когда нужно записать JSON не в строку, а напрямую в файл, HTTP-ответ или логгер.

package main

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

func main() {
    data := map[string]interface{}{
        "name": "Bob",
        "age":  25,
        "skills": []string{"Go", "Python", "Docker"},
    }

    var buf bytes.Buffer
    encoder := json.NewEncoder(&buf)
    encoder.SetIndent("", "    ") // 4 пробела для отступа
    encoder.SetEscapeHTML(false)  // отключаем экранирование HTML-символов

    err := encoder.Encode(data)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(buf.String())
}

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

  • Можно писать JSON напрямую в io.Writer (файл, сокет, HTTP-ответ).
  • Метод SetIndent позволяет менять отступы на лету.
  • SetEscapeHTML(false) полезно, если в строках есть символы <, >, & — они не будут экранироваться.

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