Avatar ·

Как получить JSON ответ от HTTP GET запроса в Go

Всем привет! Я только начал изучать Go и столкнулся с проблемой, которую не могу решить несколько часов. Мне нужно сделать GET запрос к внешнему API, который возвращает JSON, и распарсить этот ответ в структуру. Вот что я пробовал: ```go package main import ( "fmt" "net/http" ) func main() { resp, err := http.Get("https://api.example.com/data") if err != nil { fmt.Println("Error:", err) } defer resp.Body.Close() fmt.Println(resp) // Что дальше? } ``` Проблема в том, что `resp` — это не сам JSON, а какой-то объект Response. Я не понимаю, как извлечь из него данные в структуру. Пробовал читать документацию, но там много про `io.Reader` и `json.Decoder`, и я запутался. Конкретные вопросы: 1. Как правильно прочитать тело ответа? 2. Как преобразовать JSON в структуру Go? 3. Нужно ли обрабатывать статус-код ответа? 4. Как быть с ошибками, если API вернул не JSON? Буду благодарен за подробный пример с объяснением. Я привык к Python, где это делается в одну строку `requests.get().json()`, а в Go всё сложнее. Заранее спасибо! <<>>

Пошаговое решение: получаем JSON в Go

Вы на правильном пути! Действительно, в Go нужно делать несколько шагов, но это даёт больше контроля. Разберём всё по порядку.

1. Базовая структура для ответа

Сначала определим структуру, в которую будем парсить JSON. Допустим, API возвращает пользователя:

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

Обратите внимание на теги json:"fieldname" — они указывают, какому полю JSON соответствует поле структуры.

2. Полный пример с обработкой ошибок

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    // 1. Выполняем GET запрос
    resp, err := http.Get("https://api.example.com/user/1")
    if err != nil {
        fmt.Printf("Ошибка выполнения запроса: %v\n", err)
        return
    }
    defer resp.Body.Close()

    // 2. Проверяем статус-код
    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(resp.Body)
        fmt.Printf("Сервер вернул ошибку %d: %s\n", resp.StatusCode, string(body))
        return
    }

    // 3. Читаем тело ответа
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Ошибка чтения ответа: %v\n", err)
        return
    }

    // 4. Парсим JSON в структуру
    var user User
    err = json.Unmarshal(body, &user)
    if err != nil {
        fmt.Printf("Ошибка парсинга JSON: %v\n", err)
        return
    }

    // 5. Используем данные
    fmt.Printf("Пользователь: %s (ID: %d, Email: %s)\n", user.Name, user.ID, user.Email)
}

3. Альтернатива: используем json.Decoder (для потоков)

Если вы не хотите читать всё тело в память, можно использовать json.Decoder напрямую из resp.Body:

var user User
err = json.NewDecoder(resp.Body).Decode(&user)
if err != nil {
    fmt.Printf("Ошибка декодирования JSON: %v\n", err)
    return
}

4. Важные моменты

  • Всегда закрывайте resp.Body через defer resp.Body.Close(), чтобы не утекали соединения
  • Проверяйте статус-код — даже при 200 OK тело может быть пустым или содержать ошибку
  • Используйте io.ReadAll для небольших ответов (до нескольких МБ), иначе берите json.NewDecoder
  • Обрабатывайте ошибки на каждом шаге — это стандартная практика в Go

5. Пример с таймаутом (рекомендуется)

client := &http.Client{
    Timeout: 10 * time.Second,
}
resp, err := client.Get("https://api.example.com/data")

Теперь вы знаете, как получать JSON в Go. Это немного многословнее, чем в Python, но зато вы полностью контролируете процесс и обработку ошибок. Удачи!

<<>>

Использование вспомогательной функции для повторного использования

Чтобы не писать один и тот же код каждый раз, можно создать универсальную функцию для GET запросов с JSON ответом:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// GetJSON выполняет GET запрос и парсит JSON ответ в target
func GetJSON(url string, target interface{}) error {
    client := &http.Client{Timeout: 10 * time.Second}
    
    resp, err := client.Get(url)
    if err != nil {
        return fmt.Errorf("ошибка запроса: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("статус %d", resp.StatusCode)
    }

    return json.NewDecoder(resp.Body).Decode(target)
}

func main() {
    var user User
    err := GetJSON("https://api.example.com/user/1", &user)
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }
    fmt.Printf("Пользователь: %s\n", user.Name)
}

Преимущества такого подхода:

  • Переиспользование кода — одна функция для любых структур
  • Централизованная обработка таймаутов и статус-кодов
  • Простота использования — передал URL и указатель на структуру

Этот паттерн очень распространён в реальных Go проектах. Вы можете расширить функцию, добавив заголовки, логирование или поддержку разных HTTP методов.