Files
elowdb-go/CUSTOM_JSON.md

355 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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