10 KiB
10 KiB
Настраиваемые функции сериализации JSON в LineDb
Обзор
LineDb поддерживает настраиваемые функции сериализации и десериализации JSON. По умолчанию используется библиотека go-json, но вы можете указать любые функции сериализации, соответствующие определенным сигнатурам.
Функции по умолчанию
// Функции сериализации по умолчанию (используют 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:
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
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. Кастомные функции с метаданными
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. Функции с шифрованием
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 всегда первое поле
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
- Возвращает: Ошибку (если есть)
Совместимость
При использовании кастомных функций сериализации важно обеспечить совместимость:
- Обратная совместимость: Убедитесь, что новые функции могут читать данные, записанные старыми функциями
- Версионирование: Рассмотрите добавление версии в метаданные для будущих изменений
- Тестирование: Всегда тестируйте функции сериализации с реальными данными
Примеры файлов
Стандартный JSON (go-json по умолчанию)
{"id":1,"username":"user1","email":"user1@example.com"}
Стандартный JSON (encoding/json)
{"id":2,"username":"user2","email":"user2@example.com"}
Кастомный JSON с метаданными
{
"data": {"id":3,"username":"user3","email":"user3@example.com"},
"timestamp": 1754969076,
"version": "1.0"
}
JSON с ID первым полем
{"id":1,"createdAt":1754969435,"email":"user@example.com","isActive":true,"role":"user","username":"user1"}
Запуск примера
cd examples/custom-json
go run main.go
Этот пример демонстрирует:
- Использование go-json (по умолчанию)
- Использование стандартного encoding/json
- Кастомные функции сериализации с метаданными
- Функции сериализации с ID первым полем
Тестирование
cd examples/custom-json
go test -v
Тесты проверяют:
- Корректность сериализации/десериализации
- Работу с JSONLFile
- Обработку ошибок