mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 14:20:32 +01:00
JSON Store
The jsonstore package provides persistent JSON storage with namespace support, using thread-safe concurrent maps and automatic loading/saving.
Overview
The jsonstore package implements a simple yet powerful JSON storage system for GoDoxy, supporting both key-value stores (MapStore) and single object stores (ObjectStore) with automatic persistence to JSON files.
Key Features
- Namespace-based storage
- Thread-safe concurrent map operations (xsync)
- Automatic JSON loading on initialization
- Automatic JSON saving on program exit
- Generic type support
- Marshal/Unmarshal integration
Architecture
graph TD
A[JSON Store] --> B{Namespace}
B --> C[MapStore]
B --> D[ObjectStore]
C --> E[xsync.Map]
D --> F[Single Object]
G[Storage File] --> H[Load on Init]
H --> I[Parse JSON]
I --> J[xsync.Map or Object]
K[Program Exit] --> L[Save All]
L --> M[Serialize to JSON]
M --> N[Write Files]
Core Components
MapStore
type MapStore[VT any] struct {
*xsync.Map[string, VT]
}
// Implements:
// - Initialize() - initializes the internal map
// - MarshalJSON() - serializes to JSON
// - UnmarshalJSON() - deserializes from JSON
ObjectStore
type ObjectStore[Pointer Initializer] struct {
ptr Pointer
}
// Initializer interface requires:
// - Initialize()
Store Interface
type store interface {
Initialize()
json.Marshaler
json.Unmarshaler
}
Public API
MapStore Creation
// Store creates a new namespace map store.
func Store[VT any](namespace namespace) MapStore[VT]
ObjectStore Creation
// Object creates a new namespace object store.
func Object[Ptr Initializer](namespace namespace) Ptr
Usage
MapStore Example
// Define a namespace
type UserID string
// Create a store for user sessions
var sessions = jsonstore.Store[UserID]("sessions")
// Store a value
sessions.Store("user123", "session-token-abc")
// Load a value
token, ok := sessions.Load("user123")
if ok {
fmt.Println("Session:", token)
}
// Iterate over all entries
for id, token := range sessions.Range {
fmt.Printf("%s: %s\n", id, token)
}
// Delete a value
sessions.Delete("user123")
ObjectStore Example
// Define a struct that implements Initialize
type AppConfig struct {
Name string
Version int
}
func (c *AppConfig) Initialize() {
c.Name = "MyApp"
c.Version = 1
}
// Create an object store
var config = jsonstore.Object[*AppConfig]("app_config")
// Access the object
fmt.Printf("App: %s v%d\n", config.Name, config.Version)
// Modify and save (automatic on exit)
config.Version = 2
Complete Example
package main
import (
"encoding/json"
"github.com/yusing/godoxy/internal/jsonstore"
)
type Settings struct {
Theme string
Lang string
}
func (s *Settings) Initialize() {
s.Theme = "dark"
s.Lang = "en"
}
func main() {
// Create namespace type
type SettingsKey string
// Create stores
var settings = jsonstore.Object[*Settings]("settings")
var cache = jsonstore.Store[string]("cache")
// Use stores
settings.Theme = "light"
cache.Store("key1", "value1")
// On program exit, all stores are automatically saved
}
Data Flow
sequenceDiagram
participant Application
participant Store
participant xsync.Map
participant File
Application->>Store: Store(key, value)
Store->>xsync.Map: Store(key, value)
xsync.Map-->>Store: Done
Application->>Store: Load(key)
Store->>xsync.Map: Load(key)
xsync.Map-->>Store: value
Store-->>Application: value
Application->>Store: Save()
Store->>File: Marshal JSON
File-->>Store: Success
Note over Store,File: On program exit
Store->>File: Save all stores
File-->>Store: Complete
Namespace
Namespaces are string identifiers for different storage areas:
type namespace string
// Create namespaces
var (
users = jsonstore.Store[User]("users")
sessions = jsonstore.Store[Session]("sessions")
config = jsonstore.Object[*Config]("config")
metadata = jsonstore.Store[string]("metadata")
)
Reserved Names
None
File Storage
File Location
var storesPath = common.DataDir // Typically ./data/.{namespace}.json
File Format
Stores are saved as {namespace}.json:
{
"key1": "value1",
"key2": "value2"
}
Automatic Loading
On initialization, stores are loaded from disk:
func loadNS[T store](ns namespace) T {
store := reflect.New(reflect.TypeFor[T]().Elem()).Interface().(T)
store.Initialize()
path := filepath.Join(storesPath, string(ns)+".json")
file, err := os.Open(path)
if err != nil {
if !os.IsNotExist(err) {
log.Err(err).Msg("failed to load store")
}
return store
}
defer file.Close()
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
log.Err(err).Msg("failed to decode store")
}
stores[ns] = store
return store
}
Automatic Saving
On program exit, all stores are saved:
func init() {
task.OnProgramExit("save_stores", func() {
if err := save(); err != nil {
log.Error().Err(err).Msg("failed to save stores")
}
})
}
func save() error {
for ns, store := range stores {
path := filepath.Join(storesPath, string(ns)+".json")
if err := serialization.SaveJSON(path, &store, 0644); err != nil {
return err
}
}
return nil
}
Thread Safety
The MapStore uses xsync.Map for thread-safe operations:
type MapStore[VT any] struct {
*xsync.Map[string, VT]
}
// All operations are safe:
// - Load, Store, Delete
// - Range iteration
// - LoadAndDelete
// - LoadOrCompute
JSON Serialization
MarshalJSON
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
return sonic.Marshal(xsync.ToPlainMap(s.Map))
}
UnmarshalJSON
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
tmp := make(map[string]VT)
if err := sonic.Unmarshal(data, &tmp); err != nil {
return err
}
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
for k, v := range tmp {
s.Store(k, v)
}
return nil
}
Integration Points
The jsonstore package integrates with:
- Serialization: JSON marshaling/unmarshaling
- Task Management: Program exit callbacks
- Common: Data directory configuration
Error Handling
Errors are logged but don't prevent store usage:
if err := sonic.Unmarshal(data, &tmp); err != nil {
log.Err(err).
Str("path", path).
Msg("failed to load store")
}
Performance Considerations
- Uses
xsync.Mapfor lock-free reads - Presizes maps based on input data
- Sonic library for fast JSON parsing
- Background save on program exit (non-blocking)