# Настраиваемые функции сериализации 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 - Обработку ошибок