Skip to content

Commit 72e2d44

Browse files
committed
add CRUD operations and JSON regression coverage
1 parent f507bdc commit 72e2d44

5 files changed

Lines changed: 750 additions & 36 deletions

File tree

README.md

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,70 @@
77

88
## Overview
99

10-
A Golang Map that keeps track of the insert order of items.
10+
A Go map that preserves insertion order for manual operations and JSON marshalling.
11+
Nested JSON objects are unmarshalled as ordered maps too.
1112

12-
The current version can be used as a replacement for `map[string]any` and allows JSON unmarshalling into it.
13-
No manual adding of items is currently possible.
13+
## Installation
14+
15+
```bash
16+
go get github.com/cornelk/orderedmap
17+
```
18+
19+
Requires Go 1.24 or later.
1420

1521
## Usage
1622

17-
Unmarshall and marshall JSON:
23+
```go
24+
package main
25+
26+
import (
27+
"encoding/json"
28+
"fmt"
29+
30+
"github.com/cornelk/orderedmap"
31+
)
32+
33+
func main() {
34+
var m orderedmap.Map
35+
36+
m.Set("name", "Alice")
37+
m.Set("age", 30)
38+
39+
if value, ok := m.Get("name"); ok {
40+
fmt.Println(value)
41+
}
1842

43+
m.Range(func(key string, value any) bool {
44+
fmt.Printf("%s: %v\n", key, value)
45+
return true
46+
})
47+
48+
data, _ := json.Marshal(&m)
49+
fmt.Println(string(data))
50+
}
1951
```
20-
var m Map
21-
input := `{"423":"abc","231":"dbh","152":"xyz"}`
2252

23-
m.UnmarshalJSON([]byte(input))
53+
JSON input order is preserved when unmarshalling and marshalling:
54+
55+
```go
56+
var m orderedmap.Map
57+
input := `{"423":"abc","231":"dbh","152":"xyz"}`
2458

25-
output, _ := m.MarshalJSON()
26-
# unlike the standard Golang map, the output will be exact the same and not in random key order
59+
json.Unmarshal([]byte(input), &m)
60+
output, _ := json.Marshal(&m)
61+
// Output: {"423":"abc","231":"dbh","152":"xyz"}
2762
```
63+
64+
## API
65+
66+
- `Set(key string, value any)` adds or updates a value without moving existing keys.
67+
- `Get(key string) (any, bool)` retrieves a value.
68+
- `Delete(key string) bool` removes a value.
69+
- `GetAndDelete(key string) (any, bool)` retrieves and removes a value.
70+
- `Clear()` removes all values.
71+
- `Len() int` returns the number of entries.
72+
- `Range(func(key string, value any) bool)` iterates in insertion order.
73+
74+
## License
75+
76+
Apache-2.0 License - See LICENSE file for details.

map.go

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import (
99
)
1010

1111
// Map implements a map that keeps the item order.
12+
// It preserves the insertion order of keys for both JSON unmarshalling
13+
// and manual operations.
1214
type Map struct {
13-
// Data holds the map entries in unsorted order.
15+
// Data holds the map entries with their sequence indices.
1416
Data map[string]Entry
15-
// Keys contains the map keys in sorted order.
17+
// Keys contains the map keys in insertion order.
1618
Keys []string
1719
}
1820

@@ -21,9 +23,88 @@ func (m *Map) Len() int {
2123
return len(m.Keys)
2224
}
2325

26+
// Get retrieves the value for the given key.
27+
// Returns the value and true if the key exists, or nil and false otherwise.
28+
func (m *Map) Get(key string) (any, bool) {
29+
if m.Data == nil {
30+
return nil, false
31+
}
32+
entry, exists := m.Data[key]
33+
if !exists {
34+
return nil, false
35+
}
36+
return entry.Value, true
37+
}
38+
39+
// Set adds or updates a key-value pair in the map.
40+
// If the key already exists, its value is updated without changing position.
41+
// If the key is new, it is appended to the end of the insertion order.
42+
func (m *Map) Set(key string, value any) {
43+
if m.Data == nil {
44+
m.Data = make(map[string]Entry)
45+
}
46+
47+
if _, exists := m.Data[key]; !exists {
48+
m.Keys = append(m.Keys, key)
49+
}
50+
51+
m.Data[key] = Entry{
52+
index: nextSequence(),
53+
Value: value,
54+
}
55+
}
56+
57+
// Delete removes a key-value pair from the map.
58+
// Returns true if the key existed and was deleted, false otherwise.
59+
//
60+
// Performance note: This operation is O(n) where n is the number of keys,
61+
// as it requires searching through the Keys slice to find and remove the key.
62+
func (m *Map) Delete(key string) bool {
63+
if m.Data == nil {
64+
return false
65+
}
66+
67+
if _, exists := m.Data[key]; !exists {
68+
return false
69+
}
70+
71+
delete(m.Data, key)
72+
73+
for i, k := range m.Keys {
74+
if k == key {
75+
m.Keys = append(m.Keys[:i], m.Keys[i+1:]...)
76+
break
77+
}
78+
}
79+
80+
return true
81+
}
82+
83+
// GetAndDelete retrieves and removes a key in one operation.
84+
// Returns the value and true if the key existed, or nil and false otherwise.
85+
//
86+
// Performance note: This operation is O(n) due to the underlying Delete operation.
87+
func (m *Map) GetAndDelete(key string) (value any, loaded bool) {
88+
value, loaded = m.Get(key)
89+
if loaded {
90+
m.Delete(key)
91+
}
92+
return value, loaded
93+
}
94+
95+
// Clear removes all entries from the map, resulting in an empty map.
96+
func (m *Map) Clear() {
97+
m.Data = make(map[string]Entry)
98+
m.Keys = nil
99+
}
100+
24101
// Range calls f sequentially for each key and value present in the map.
25102
// If f returns false, range stops the iteration.
26103
func (m *Map) Range(f func(key string, value any) bool) {
104+
if m.Data == nil || len(m.Keys) == 0 {
105+
return
106+
}
107+
27108
for _, key := range m.Keys {
28109
entry := m.Data[key]
29110
if !f(key, entry.Value) {
@@ -44,31 +125,41 @@ func (m *Map) UnmarshalJSON(b []byte) error {
44125

45126
// MarshalJSON implements the json.Marshaler interface.
46127
func (m *Map) MarshalJSON() ([]byte, error) {
47-
var buf bytes.Buffer
128+
if m == nil || len(m.Keys) == 0 {
129+
return []byte("{}"), nil
130+
}
131+
132+
buf := bytes.NewBuffer(make([]byte, 0, len(m.Keys)*20))
48133
buf.WriteString("{")
49134

135+
lastIdx := len(m.Keys) - 1
50136
for i, key := range m.Keys {
51-
buf.WriteString(fmt.Sprintf("%q:", key))
137+
keyData, err := json.Marshal(key)
138+
if err != nil {
139+
return nil, fmt.Errorf("marshalling key %q: %w", key, err)
140+
}
141+
buf.Write(keyData)
142+
buf.WriteByte(':')
52143

53144
value := m.Data[key].Value
54145
b, err := json.Marshal(value)
55146
if err != nil {
56-
return nil, fmt.Errorf("marshalling entry: %w", err)
147+
return nil, fmt.Errorf("marshalling entry for key %q: %w", key, err)
57148
}
58149
buf.Write(b)
59150

60-
if i < len(m.Keys)-1 {
61-
buf.WriteString(",")
151+
if i < lastIdx {
152+
buf.WriteByte(',')
62153
}
63154
}
64155

65-
buf.WriteString("}")
156+
buf.WriteByte('}')
66157
return buf.Bytes(), nil
67158
}
68159

69-
// rebuildKeys build the sorted keys slice.
160+
// rebuildKeys builds the keys slice sorted by insertion order.
70161
func (m *Map) rebuildKeys() {
71-
m.Keys = []string{}
162+
m.Keys = make([]string, 0, len(m.Data))
72163
for name := range m.Data {
73164
m.Keys = append(m.Keys, name)
74165
}

0 commit comments

Comments
 (0)