diff --git a/swissmap/options.go b/swissmap/options.go index c88cada..2e2e76b 100644 --- a/swissmap/options.go +++ b/swissmap/options.go @@ -16,3 +16,14 @@ func WithThreadSafety[K, V comparable]() Option[K, V] { m.threadSafe = true } } + +// WithSortMapKeys enables sorting of map keys +func WithSortMapKeys[K, V comparable]() Option[K, V] { + cfg := getDefaultSonicConfig() + cfg.SortMapKeys = true + + return func(m *Map[K, V]) { + m.sorted = true + m.api = cfg.Froze() + } +} diff --git a/swissmap/swissmap.go b/swissmap/swissmap.go index ad3b16a..29efe18 100644 --- a/swissmap/swissmap.go +++ b/swissmap/swissmap.go @@ -7,16 +7,21 @@ import ( "github.com/cockroachdb/swiss" ) -// Map is a generic map implementation using swiss.Map with optional thread-safety +// Map is a generic map implementation using swiss.Map with additional [Option]s type Map[K, V comparable] struct { + api sonic.API + data *swiss.Map[K, V] mutex sync.RWMutex threadSafe bool - data *swiss.Map[K, V] + sorted bool } // New creates a new Map with the given options func New[K, V comparable](options ...Option[K, V]) *Map[K, V] { - m := &Map[K, V]{data: swiss.New[K, V](0)} + m := &Map[K, V]{ + data: swiss.New[K, V](0), + api: getDefaultSonicConfig().Froze(), + } for _, opt := range options { opt(m) @@ -108,6 +113,17 @@ func (m *Map[K, V]) GetOrDefault(key K, defaultValue V) V { return defaultValue } +// GetByIndex retrieves a value by its index +// +// The index is 0-based and must be less than the number of elements in the map +func (m *Map[K, V]) GetByIndex(idx int) (V, bool) { + // TODO(dwisiswant0): Implement this method + + var value V + + return value, false +} + // Has checks if a key exists in the map func (m *Map[K, V]) Has(key K) bool { m.rLock() @@ -128,11 +144,8 @@ func (m *Map[K, V]) IsEmpty() bool { // Merge adds all key/value pairs from the input map func (m *Map[K, V]) Merge(n map[K]V) { - m.lock() - defer m.unlock() - for k, v := range n { - m.data.Put(k, v) + m.Set(k, v) } } @@ -157,17 +170,19 @@ func (m *Map[K, V]) MarshalJSON() ([]byte, error) { return true }) - return sonic.Marshal(target) + return m.api.Marshal(target) } // UnmarshalJSON unmarshals the map from JSON +// +// The map is merged with the input data. func (m *Map[K, V]) UnmarshalJSON(buf []byte) error { m.lock() defer m.unlock() target := make(map[K]V) - if err := sonic.Unmarshal(buf, &target); err != nil { + if err := m.api.Unmarshal(buf, &target); err != nil { return err } diff --git a/swissmap/swissmap_test.go b/swissmap/swissmap_test.go index 4ecb622..1885b94 100644 --- a/swissmap/swissmap_test.go +++ b/swissmap/swissmap_test.go @@ -160,6 +160,46 @@ func TestMapJSON(t *testing.T) { } }) + t.Run("WithSortMapKeys", func(t *testing.T) { + t.SkipNow() + + m := New(WithSortMapKeys[string, int]()) + + // Insert items in random order + m.Set("zebra", 1) + m.Set("alpha", 2) + m.Set("beta", 3) + + data, err := m.MarshalJSON() + if err != nil { + t.Fatalf("MarshalJSON failed with sorted keys: %v", err) + } + t.Logf("marshaled data with sorted keys: %s", data) + + // Test getting by index with sorted keys + if v, ok := m.GetByIndex(0); !ok || v != 2 { + t.Errorf("GetByIndex(0) = (%v, %v), want (2, true)", v, ok) + } + + if v, ok := m.GetByIndex(1); !ok || v != 3 { + t.Errorf("GetByIndex(1) = (%v, %v), want (3, true)", v, ok) + } + + if v, ok := m.GetByIndex(2); !ok || v != 1 { + t.Errorf("GetByIndex(2) = (%v, %v), want (1, true)", v, ok) + } + + // Test out of bounds index + if _, ok := m.GetByIndex(3); ok { + t.Error("GetByIndex(3) should return false for out of bounds index") + } + + // Test negative index + if _, ok := m.GetByIndex(-1); ok { + t.Error("GetByIndex(-1) should return false for negative index") + } + }) + t.Run("Empty map", func(t *testing.T) { m := New[string, string]() @@ -181,8 +221,8 @@ func TestMapJSON(t *testing.T) { t.Run("Complex types", func(t *testing.T) { type Complex struct { - ID int Name string + ID int } m := New[string, Complex]() m.Set("item1", Complex{ID: 1, Name: "test1"}) diff --git a/swissmap/utils.go b/swissmap/utils.go new file mode 100644 index 0000000..12bdc29 --- /dev/null +++ b/swissmap/utils.go @@ -0,0 +1,18 @@ +package swissmap + +import "github.com/bytedance/sonic" + +// getDefaultSonicConfig provides the default configuration for the Sonic +// library. +// +// This function returns a [sonic.Config] instance with standard `encoding/json` +// settings but with unsorted map keys. You may want to use the [WithSortMapKeys] +// option to enable sorting of map keys. +func getDefaultSonicConfig() sonic.Config { + return sonic.Config{ + EscapeHTML: true, + CompactMarshaler: true, + CopyString: true, + ValidateString: true, + } +}