Работа с JSON в Go

Полное руководство по работе с JSON в Go: файлы, API и базы данных

1. Основы работы с JSON в Go

Структуры для маршалинга/анмаршалинга

type User struct {
    ID       int      `json:"id"`
    Name     string   `json:"name"`
    Email    string   `json:"email"`
    Roles    []string `json:"roles"`
    IsActive bool     `json:"is_active"`
}

Преобразование структур в JSON

user := User{
    ID:       1,
    Name:    "Alice",
    Email:   "alice@example.com",
    Roles:   []string{"admin", "user"},
    IsActive: true,
}

jsonData, err := json.Marshal(user)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(jsonData))
// Output: {"id":1,"name":"Alice","email":"alice@example.com","roles":["admin","user"],"is_active":true}

Чтение JSON в структуру

var decodedUser User
err = json.Unmarshal(jsonData, &decodedUser)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%+v\n", decodedUser)

2. Работа с JSON-файлами

Запись JSON в файл

func writeJSON(filename string, data interface{}) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := json.NewEncoder(file)
    encoder.SetIndent("", "  ") // Форматирование с отступами
    return encoder.Encode(data)
}

// Использование
err := writeJSON("user.json", user)

Чтение JSON из файла

func readJSON(filename string, target interface{}) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    return json.NewDecoder(file).Decode(target)
}

// Использование
var userFromFile User
err := readJSON("user.json", &userFromFile)

3. Обработка директорий с JSON-файлами

Чтение всех JSON-файлов в директории

func readAllJSONFiles(dirPath string) ([]User, error) {
    var users []User

    files, err := os.ReadDir(dirPath)
    if err != nil {
        return nil, err
    }

    for _, file := range files {
        if !strings.HasSuffix(file.Name(), ".json") {
            continue
        }

        var user User
        err := readJSON(filepath.Join(dirPath, file.Name()), &user)
        if err != nil {
            return nil, err
        }

        users = append(users, user)
    }

    return users, nil
}

Пример: Сохранение множества файлов

users := []User{
    {ID: 1, Name: "Alice"},
    {ID: 2, Name: "Bob"},
}

for _, user := range users {
    filename := fmt.Sprintf("user_%d.json", user.ID)
    err := writeJSON(filename, user)
    if err != nil {
        log.Printf("Error saving %s: %v", filename, err)
    }
}

4. Взаимодействие с API

Отправка JSON через HTTP POST

func sendUserToAPI(url string, user User) (*http.Response, error) {
    jsonData, err := json.Marshal(user)
    if err != nil {
        return nil, err
    }

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    if err != nil {
        return nil, err
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer token123")

    client := &http.Client{}
    return client.Do(req)
}

// Использование
resp, err := sendUserToAPI("https://api.example.com/users", user)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

Чтение JSON-ответа от API

func getUsersFromAPI(url string) ([]User, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("API returned status: %s", resp.Status)
    }

    var users []User
    err = json.NewDecoder(resp.Body).Decode(&users)
    return users, err
}

5. Продвинутые техники

Кастомный парсинг дат в JSON

type CustomTime time.Time

func (ct *CustomTime) UnmarshalJSON(b []byte) error {
    s := strings.Trim(string(b), `"`)
    t, err := time.Parse("2006-01-02", s)
    if err != nil {
        return err
    }
    *ct = CustomTime(t)
    return nil
}

type Event struct {
    Name string     `json:"name"`
    Date CustomTime `json:"date"`
}

Обработка динамических JSON-структур

var data map[string]interface{}
err := json.Unmarshal(jsonData, &data)
if err != nil {
    log.Fatal(err)
}

if name, ok := data["name"].(string); ok {
    fmt.Println("Name:", name)
}

6. Полный пример: CRUD с JSON-файлами

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
    "path/filepath"
)

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

const storageDir = "data/users"

func init() {
    os.MkdirAll(storageDir, 0755)
}

func saveUser(user User) error {
    filename := filepath.Join(storageDir, fmt.Sprintf("%d.json", user.ID))
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := json.NewEncoder(file)
    encoder.SetIndent("", "  ")
    return encoder.Encode(user)
}

func getUser(id int) (User, error) {
    var user User
    filename := filepath.Join(storageDir, fmt.Sprintf("%d.json", id))
    
    file, err := os.Open(filename)
    if err != nil {
        return user, err
    }
    defer file.Close()

    err = json.NewDecoder(file).Decode(&user)
    return user, err
}

func main() {
    user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
    
    // Create
    if err := saveUser(user); err != nil {
        log.Fatal(err)
    }
    
    // Read
    savedUser, err := getUser(1)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("User: %+v\n", savedUser)
}

7. Оптимизация работы с большими JSON

Стриминг при записи

func writeLargeJSON(filename string, data []User) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    encoder := json.NewEncoder(file)
    for _, item := range data {
        if err := encoder.Encode(item); err != nil {
            return err
        }
    }
    return nil
}

Построчное чтение больших файлов

func readLargeJSON(filename string) ([]User, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var users []User
    decoder := json.NewDecoder(file)
    for decoder.More() {
        var user User
        if err := decoder.Decode(&user); err != nil {
            return nil, err
        }
        users = append(users, user)
    }

    return users, nil
}

8. Безопасность и валидация

Валидация JSON-схемы

import "github.com/xeipuuv/gojsonschema"

func validateJSON(schemaPath string, jsonData []byte) error {
    schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath)
    documentLoader := gojsonschema.NewBytesLoader(jsonData)

    result, err := gojsonschema.Validate(schemaLoader, documentLoader)
    if err != nil {
        return err
    }

    if !result.Valid() {
        return fmt.Errorf("validation errors: %v", result.Errors())
    }

    return nil
}

Защита от переполнения при парсинге

decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields() // Запрет неизвестных полей

Полезные материалы:

  • JSON Schema для сложной валидации
  • Альтернативные JSON-парсеры: json-iterator
  • Бинарные JSON-альтернативы: Protocol Buffers, MessagePack