-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 70d9289
Showing
10 changed files
with
548 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
ISC License | ||
|
||
Copyright (c) 2024 Nicolás Parada | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/nicolasparada/go-ordered-map.svg)](https://pkg.go.dev/github.com/nicolasparada/go-ordered-map) | ||
|
||
# Golang Ordered Map | ||
|
||
Golang Ordered Map is a `map` data structure that maintains the order of the keys. | ||
It also supports JSON and YAML marshalling. | ||
|
||
## Installation | ||
|
||
```bash | ||
go get github.com/nicolasparada/go-ordered-map | ||
``` | ||
|
||
## Usage | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
orderedmap "github.com/nicolasparada/go-ordered-map" | ||
) | ||
|
||
func main() { | ||
data := []byte(`{ "name": "John", "age": 30, "active": true }`) | ||
|
||
var unordered map[string]any{} | ||
if err := json.Unmarshal(data, &unordered); err != nil { | ||
panic(err) | ||
} | ||
|
||
var ordered orderedmap.OrderedMap[string, any] | ||
if err := json.Unmarshal(data, &ordered); err != nil { | ||
panic(err) | ||
} | ||
|
||
json.NewEncoder(os.Stdout).Encode(unordered) // will print in undefined order | ||
json.NewEncoder(os.Stdout).Encode(ordered) // will always print: {"name":"John","age":30,"active":true} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module github.com/nicolasparada/go-ordered-map | ||
|
||
go 1.22.0 | ||
|
||
require ( | ||
github.com/alecthomas/assert/v2 v2.5.0 | ||
gopkg.in/yaml.v3 v3.0.1 | ||
) | ||
|
||
require ( | ||
github.com/alecthomas/repr v0.3.0 // indirect | ||
github.com/hexops/gotextdiff v1.0.3 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/alecthomas/assert/v2 v2.5.0 h1:OJKYg53BQx06/bMRBSPDCO49CbCDNiUQXwdoNrt6x5w= | ||
github.com/alecthomas/assert/v2 v2.5.0/go.mod h1:fw5suVxB+wfYJ3291t0hRTqtGzFYdSwstnRQdaQx2DM= | ||
github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8= | ||
github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= | ||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package orderedmap | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
) | ||
|
||
// MarshalJSON implements json.Marshaler interface | ||
// to marshall a sorted list of key-value pairs into an object. | ||
func (o OrderedMap[K, V]) MarshalJSON() ([]byte, error) { | ||
if o == nil { | ||
return []byte(`null`), nil | ||
} | ||
|
||
if len(o) == 0 { | ||
return []byte(`{}`), nil | ||
} | ||
|
||
var buf bytes.Buffer | ||
if err := buf.WriteByte('{'); err != nil { | ||
return nil, err | ||
} | ||
|
||
enc := json.NewEncoder(&buf) | ||
enc.SetEscapeHTML(false) | ||
for i, p := range o { | ||
if i != 0 { | ||
if err := buf.WriteByte(','); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if err := enc.Encode(p.Key); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := buf.WriteByte(':'); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := enc.Encode(p.Val); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if err := buf.WriteByte('}'); err != nil { | ||
return nil, err | ||
} | ||
|
||
return bytes.TrimRight(buf.Bytes(), "\n"), nil | ||
} | ||
|
||
// UnmarshalJSON implements json.Unmarshaler interface | ||
// to unmarshal an object into a sorted list of key-value pairs. | ||
func (o *OrderedMap[K, V]) UnmarshalJSON(data []byte) error { | ||
var m map[K]V | ||
err := json.Unmarshal(data, &m) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
dec := json.NewDecoder(bytes.NewReader(data)) | ||
t, err := dec.Token() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if t != json.Delim('{') { | ||
return errors.New("expected start of object") | ||
} | ||
|
||
for { | ||
t, err := dec.Token() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if t == json.Delim('}') { | ||
break | ||
} | ||
|
||
key, ok := t.(K) | ||
if !ok { | ||
return fmt.Errorf("expected object key to be %T, got %T", key, t) | ||
} | ||
|
||
*o = append(*o, Pair[K, V]{ | ||
Key: key, | ||
Val: m[key], | ||
}) | ||
|
||
// ignored value | ||
if err := skipJSONValue(dec); err != nil { | ||
if errors.Is(err, io.EOF) { | ||
break | ||
} | ||
|
||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
var errJSONEnd = errors.New("invalid end of json array or object") | ||
|
||
func skipJSONValue(dec *json.Decoder) error { | ||
t, err := dec.Token() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
switch t { | ||
case json.Delim('['), json.Delim('{'): | ||
for { | ||
if err := skipJSONValue(dec); err != nil { | ||
if errors.Is(err, errJSONEnd) { | ||
break | ||
} | ||
return err | ||
} | ||
} | ||
case json.Delim(']'), json.Delim('}'): | ||
return errJSONEnd | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package orderedmap | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
"time" | ||
|
||
"github.com/alecthomas/assert/v2" | ||
) | ||
|
||
func TestOrderedMap_MarshalJSON(t *testing.T) { | ||
now := time.Now().UTC().Truncate(time.Second) | ||
got, err := json.Marshal(OrderedMap[string, any]{ | ||
{"name", "John"}, | ||
{"age", 30}, | ||
{"active", true}, | ||
{"last_access_time", now}, | ||
}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, `{"name":"John","age":30,"active":true,"last_access_time":"`+now.Format(time.RFC3339Nano)+`"}`, string(got)) | ||
} | ||
|
||
func TestOrderedMap_UnmarshalJSON(t *testing.T) { | ||
now := time.Now().UTC().Truncate(time.Second) | ||
nowStr := now.Format(time.RFC3339Nano) | ||
var got OrderedMap[string, any] | ||
err := json.Unmarshal([]byte(`{"name":"John","age":30,"active":true,"last_access_time":"`+nowStr+`"}`), &got) | ||
assert.NoError(t, err) | ||
|
||
assert.Equal(t, OrderedMap[string, any]{ | ||
{"name", "John"}, | ||
{"age", float64(30)}, // json always unmarshals numbers as float64 | ||
{"active", true}, | ||
{"last_access_time", nowStr}, // json doesn't detect time.Time | ||
}, got) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package orderedmap | ||
|
||
type OrderedMap[K comparable, V any] []Pair[K, V] | ||
|
||
type Pair[K comparable, V any] struct { | ||
Key K | ||
Val V | ||
} | ||
|
||
func New[K comparable, V any]() OrderedMap[K, V] { | ||
return OrderedMap[K, V]{} | ||
} | ||
|
||
func (m OrderedMap[K, V]) Has(key K) bool { | ||
_, ok := m.Get(key) | ||
return ok | ||
} | ||
|
||
func (m OrderedMap[K, V]) Get(key K) (V, bool) { | ||
for _, p := range m { | ||
if p.Key == key { | ||
return p.Val, true | ||
} | ||
} | ||
var zero V | ||
return zero, false | ||
} | ||
|
||
func (m *OrderedMap[K, V]) Set(key K, val V) { | ||
if m == nil { | ||
*m = OrderedMap[K, V]{} | ||
} | ||
|
||
for i, p := range *m { | ||
if p.Key == key { | ||
(*m)[i].Val = val | ||
return | ||
} | ||
} | ||
*m = append(*m, Pair[K, V]{key, val}) | ||
} | ||
|
||
func (m *OrderedMap[K, V]) Delete(key K) { | ||
if m == nil || len(*m) == 0 { | ||
return | ||
} | ||
|
||
for i, p := range *m { | ||
if p.Key == key { | ||
*m = append((*m)[:i], (*m)[i+1:]...) | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (m OrderedMap[K, V]) Keys() []K { | ||
if m == nil { | ||
return nil | ||
} | ||
keys := make([]K, len(m)) | ||
for i, p := range m { | ||
keys[i] = p.Key | ||
} | ||
return keys | ||
} | ||
|
||
func (m OrderedMap[K, V]) Values() []V { | ||
if m == nil { | ||
return nil | ||
} | ||
values := make([]V, len(m)) | ||
for i, p := range m { | ||
values[i] = p.Val | ||
} | ||
return values | ||
} | ||
|
||
func (m *OrderedMap[K, V]) Clear() { | ||
if m == nil || len(*m) == 0 { | ||
return | ||
} | ||
|
||
*m = []Pair[K, V]{} | ||
} | ||
|
||
func (m OrderedMap[K, V]) Copy() OrderedMap[K, V] { | ||
if m == nil { | ||
return nil | ||
} | ||
|
||
return append([]Pair[K, V]{}, m...) | ||
} |
Oops, something went wrong.