package nigiri import ( "encoding/json" "fmt" "os" "path/filepath" "reflect" "sort" ) // LoadFromJSON loads items from JSON using reflection func (bs *BaseStore[T]) LoadFromJSON(filename string) error { bs.mu.Lock() defer bs.mu.Unlock() data, err := os.ReadFile(filename) if err != nil { if os.IsNotExist(err) { return nil } return fmt.Errorf("failed to read JSON: %w", err) } if len(data) == 0 { return nil } // Create slice of pointers to T sliceType := reflect.SliceOf(reflect.PointerTo(bs.itemType)) slicePtr := reflect.New(sliceType) if err := json.Unmarshal(data, slicePtr.Interface()); err != nil { return fmt.Errorf("failed to unmarshal JSON: %w", err) } // Clear existing data bs.items = make(map[int]*T) bs.maxID = 0 // Clear unique indices for fieldName := range bs.uniqueIndices { bs.uniqueIndices[fieldName] = make(map[any]int) } // Extract items using reflection slice := slicePtr.Elem() for i := 0; i < slice.Len(); i++ { item := slice.Index(i).Interface().(*T) // Get ID using reflection itemValue := reflect.ValueOf(item).Elem() idField := itemValue.FieldByName("ID") if !idField.IsValid() { return fmt.Errorf("item type must have an ID field") } id := int(idField.Int()) bs.items[id] = item if id > bs.maxID { bs.maxID = id } // Update unique indices bs.updateUniqueIndices(id, item, true) } return nil } // SaveToJSON saves items to JSON atomically with consistent ID ordering func (bs *BaseStore[T]) SaveToJSON(filename string) error { bs.mu.RLock() defer bs.mu.RUnlock() // Get sorted IDs for consistent ordering ids := make([]int, 0, len(bs.items)) for id := range bs.items { ids = append(ids, id) } sort.Ints(ids) // Build items slice in ID order items := make([]*T, 0, len(bs.items)) for _, id := range ids { items = append(items, bs.items[id]) } data, err := json.MarshalIndent(items, "", "\t") if err != nil { return fmt.Errorf("failed to marshal to JSON: %w", err) } // Atomic write tempFile := filename + ".tmp" if err := os.WriteFile(tempFile, data, 0644); err != nil { return fmt.Errorf("failed to write temp JSON: %w", err) } if err := os.Rename(tempFile, filename); err != nil { os.Remove(tempFile) return fmt.Errorf("failed to rename temp JSON: %w", err) } return nil } // LoadData loads from JSON file or starts empty func (bs *BaseStore[T]) LoadData(dataPath string) error { if err := bs.LoadFromJSON(dataPath); err != nil { if os.IsNotExist(err) { fmt.Println("No existing data found, starting with empty store") return nil } return fmt.Errorf("failed to load from JSON: %w", err) } fmt.Printf("Loaded %d items from %s\n", len(bs.items), dataPath) bs.RebuildIndices() // Rebuild indices after loading return nil } // SaveData saves to JSON file func (bs *BaseStore[T]) SaveData(dataPath string) error { // Ensure directory exists dataDir := filepath.Dir(dataPath) if err := os.MkdirAll(dataDir, 0755); err != nil { return fmt.Errorf("failed to create data directory: %w", err) } if err := bs.SaveToJSON(dataPath); err != nil { return fmt.Errorf("failed to save to JSON: %w", err) } fmt.Printf("Saved %d items to %s\n", len(bs.items), dataPath) return nil }