init elowdb go-port commit
This commit is contained in:
354
CUSTOM_JSON.md
Normal file
354
CUSTOM_JSON.md
Normal file
@@ -0,0 +1,354 @@
|
||||
# Настраиваемые функции сериализации 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
|
||||
- Обработку ошибок
|
||||
Reference in New Issue
Block a user