# Настраиваемые функции сериализации JSON в LineDb

## Обзор

LineDb поддерживает настраиваемые функции сериализации и десериализации JSON. По умолчанию используется библиотека [go-json](https://github.com/goccy/go-json), но вы можете указать любые функции сериализации, соответствующие определенным сигнатурам.

## Функции по умолчанию

```go
// Функции сериализации по умолчанию (используют go-json)
func defaultJSONMarshal(v any) ([]byte, error) {
    return json.Marshal(v)
}

func defaultJSONUnmarshal(data []byte, v any) error {
    return json.Unmarshal(data, v)
}
```

## Настройка в опциях коллекции

Вы можете указать пользовательские функции сериализации в `JSONLFileOptions`:

```go
type JSONLFileOptions struct {
    CollectionName          string   `json:"collectionName,omitempty"`
    AllocSize               int      `json:"allocSize,omitempty"`
    IndexedFields           []string `json:"indexedFields,omitempty"`
    EncryptKeyForLineDb     string   `json:"encryptKeyForLineDb,omitempty"`
    SkipInvalidLines        bool     `json:"skipInvalidLines,omitempty"`
    DecryptKey              string   `json:"decryptKey,omitempty"`
    ConvertStringIdToNumber bool     `json:"convertStringIdToNumber,omitempty"`
    // Функции сериализации и десериализации JSON
    JSONMarshal   func(any) ([]byte, error) `json:"-"`
    JSONUnmarshal func([]byte, any) error   `json:"-"`
}
```

## Примеры использования

### 1. Использование стандартного encoding/json

```go
import "encoding/json"

// Создаем функции сериализации с использованием стандартного encoding/json
standardJSONMarshal := func(v any) ([]byte, error) {
    return json.Marshal(v)
}

standardJSONUnmarshal := func(data []byte, v any) error {
    return json.Unmarshal(data, v)
}

// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
    Collections: []linedb.JSONLFileOptions{
        {
            CollectionName: "users",
            JSONMarshal:    standardJSONMarshal,
            JSONUnmarshal:  standardJSONUnmarshal,
        },
    },
}
```

### 2. Кастомные функции с метаданными

```go
import "encoding/json"
import "time"

// Создаем кастомные функции сериализации с дополнительной логикой
customJSONMarshal := func(v any) ([]byte, error) {
    // Добавляем метаданные к сериализации
    data := map[string]any{
        "data":      v,
        "timestamp": time.Now().Unix(),
        "version":   "1.0",
    }
    return json.Marshal(data)
}

customJSONUnmarshal := func(data []byte, v any) error {
    // Извлекаем данные из кастомного формата
    var wrapper map[string]any
    if err := json.Unmarshal(data, &wrapper); err != nil {
        return err
    }

    // Извлекаем основную часть данных
    if dataField, exists := wrapper["data"]; exists {
        // Сериализуем обратно в JSON и десериализуем в целевой тип
        dataBytes, err := json.Marshal(dataField)
        if err != nil {
            return err
        }
        return json.Unmarshal(dataBytes, v)
    }

    // Если нет поля data, пытаемся десериализовать как обычный JSON
    return json.Unmarshal(data, v)
}

// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
    Collections: []linedb.JSONLFileOptions{
        {
            CollectionName: "users",
            JSONMarshal:    customJSONMarshal,
            JSONUnmarshal:  customJSONUnmarshal,
        },
    },
}
```

### 3. Функции с шифрованием

```go
import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io"
)

var encryptionKey = []byte("your-32-byte-encryption-key-here")

func encryptedJSONMarshal(v any) ([]byte, error) {
    // Сначала сериализуем в JSON
    jsonData, err := json.Marshal(v)
    if err != nil {
        return nil, err
    }

    // Создаем AES cipher
    block, err := aes.NewCipher(encryptionKey)
    if err != nil {
        return nil, err
    }

    // Создаем GCM
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    // Создаем nonce
    nonce := make([]byte, gcm.NonceSize())
    if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
        return nil, err
    }

    // Шифруем данные
    ciphertext := gcm.Seal(nonce, nonce, jsonData, nil)

    // Кодируем в base64
    return []byte(base64.StdEncoding.EncodeToString(ciphertext)), nil
}

func encryptedJSONUnmarshal(data []byte, v any) error {
    // Декодируем из base64
    ciphertext, err := base64.StdEncoding.DecodeString(string(data))
    if err != nil {
        return err
    }

    // Создаем AES cipher
    block, err := aes.NewCipher(encryptionKey)
    if err != nil {
        return err
    }

    // Создаем GCM
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return err
    }

    // Извлекаем nonce
    nonceSize := gcm.NonceSize()
    if len(ciphertext) < nonceSize {
        return fmt.Errorf("ciphertext too short")
    }

    nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]

    // Расшифровываем данные
    plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    if err != nil {
        return err
    }

    // Десериализуем JSON
    return json.Unmarshal(plaintext, v)
}

// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
    Collections: []linedb.JSONLFileOptions{
        {
            CollectionName: "secure_users",
            JSONMarshal:    encryptedJSONMarshal,
            JSONUnmarshal:  encryptedJSONUnmarshal,
        },
    },
}
```

### 4. ID всегда первое поле

```go
import (
    "encoding/json"
    "fmt"
    "sort"
    "strings"
)

func idFirstJSONMarshal(v any) ([]byte, error) {
    if data, ok := v.(map[string]any); ok {
        var parts []string
        
        // Сначала добавляем id если он есть
        if id, exists := data["id"]; exists {
            idBytes, err := json.Marshal(id)
            if err != nil {
                return nil, err
            }
            parts = append(parts, fmt.Sprintf(`"id":%s`, string(idBytes)))
        }
        
        // Затем добавляем все остальные поля в алфавитном порядке
        var keys []string
        for key := range data {
            if key != "id" {
                keys = append(keys, key)
            }
        }
        sort.Strings(keys)
        
        for _, key := range keys {
            valueBytes, err := json.Marshal(data[key])
            if err != nil {
                return nil, err
            }
            parts = append(parts, fmt.Sprintf(`"%s":%s`, key, string(valueBytes)))
        }
        
        jsonStr := "{" + strings.Join(parts, ",") + "}"
        return []byte(jsonStr), nil
    }
    
    // Если это не map, используем стандартную сериализацию
    return json.Marshal(v)
}

func idFirstJSONUnmarshal(data []byte, v any) error {
    return json.Unmarshal(data, v)
}

// Настройка в опциях инициализации
initOptions := &linedb.LineDbInitOptions{
    Collections: []linedb.JSONLFileOptions{
        {
            CollectionName: "users",
            JSONMarshal:    idFirstJSONMarshal,
            JSONUnmarshal:  idFirstJSONUnmarshal,
        },
    },
}
```

## Требования к функциям

### JSONMarshal

- **Сигнатура**: `func(any) ([]byte, error)`
- **Назначение**: Сериализует данные в JSON
- **Возвращает**: Байты JSON и ошибку (если есть)

### JSONUnmarshal

- **Сигнатура**: `func([]byte, any) error`
- **Назначение**: Десериализует данные из JSON
- **Возвращает**: Ошибку (если есть)

## Совместимость

При использовании кастомных функций сериализации важно обеспечить совместимость:

1. **Обратная совместимость**: Убедитесь, что новые функции могут читать данные, записанные старыми функциями
2. **Версионирование**: Рассмотрите добавление версии в метаданные для будущих изменений
3. **Тестирование**: Всегда тестируйте функции сериализации с реальными данными

## Примеры файлов

### Стандартный JSON (go-json по умолчанию)

```json
{"id":1,"username":"user1","email":"user1@example.com"}
```

### Стандартный JSON (encoding/json)

```json
{"id":2,"username":"user2","email":"user2@example.com"}
```

### Кастомный JSON с метаданными

```json
{
  "data": {"id":3,"username":"user3","email":"user3@example.com"},
  "timestamp": 1754969076,
  "version": "1.0"
}
```

### JSON с ID первым полем

```json
{"id":1,"createdAt":1754969435,"email":"user@example.com","isActive":true,"role":"user","username":"user1"}
```

## Запуск примера

```bash
cd examples/custom-json
go run main.go
```

Этот пример демонстрирует:

- Использование go-json (по умолчанию)
- Использование стандартного encoding/json
- Кастомные функции сериализации с метаданными
- Функции сериализации с ID первым полем

## Тестирование

```bash
cd examples/custom-json
go test -v
```

Тесты проверяют:

- Корректность сериализации/десериализации
- Работу с JSONLFile
- Обработку ошибок
