From 280d4a8d6fe435ef9b67a743fd196c1d2e29e0a8 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Mon, 30 Oct 2023 13:01:43 +0100 Subject: [PATCH] Restructured to enable custom types injection on Marshal and Unmarshal The core is the basic functionality of the map --- go.sum | 0 orderedmap-core.go | 232 +++++++++++++++++++++++++++++++++++++ orderedmap.go | 278 ++++++++------------------------------------- orderedmap_test.go | 95 ++++------------ pair.go | 23 ++++ 5 files changed, 323 insertions(+), 305 deletions(-) create mode 100644 go.sum create mode 100644 orderedmap-core.go create mode 100644 pair.go diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/orderedmap-core.go b/orderedmap-core.go new file mode 100644 index 0000000..0f2d1b1 --- /dev/null +++ b/orderedmap-core.go @@ -0,0 +1,232 @@ +package orderedmap + +import ( + "bytes" + "encoding/json" + "io" +) + +type OrderedMapCore interface { + CoreKeys() []string + CoreSetKeys([]string) + CoreValues() map[string]interface{} + CoreSetValues(map[string]interface{}) + CoreGetKey(key string) (interface{}, bool) + CoreSetKey(key string, value interface{}) + CoreDeleteKey(key string) +} + +type CloneFunc func(...map[string]interface{}) OrderedMapCore + +type orderedMapCore struct { + keys []string + values map[string]interface{} +} + +func NewCore(oms ...map[string]interface{}) OrderedMapCore { + var om map[string]interface{} + if len(oms) > 0 { + om = oms[0] + } else { + om = make(map[string]interface{}) + } + return &orderedMapCore{ + keys: make([]string, 0, len(om)), + values: om, + } +} + +func (o *orderedMapCore) CoreGetKey(key string) (interface{}, bool) { + val, exists := o.values[key] + return val, exists +} + +func (o *orderedMapCore) CoreSetKey(key string, value interface{}) { + _, exists := o.values[key] + if !exists { + o.keys = append(o.keys, key) + } + o.values[key] = value +} + +func (o *orderedMapCore) CoreDeleteKey(key string) { + // check key is in use + _, ok := o.values[key] + if !ok { + return + } + // remove from keys + for i, k := range o.keys { + if k == key { + o.keys = append(o.keys[:i], o.keys[i+1:]...) + break + } + } + // remove from values + delete(o.values, key) +} + +func (o *orderedMapCore) CoreKeys() []string { + return o.keys +} + +func (o *orderedMapCore) CoreSetKeys(keys []string) { + o.keys = keys +} + +func (o *orderedMapCore) CoreValues() map[string]interface{} { + return o.values +} + +func (o *orderedMapCore) CoreSetValues(values map[string]interface{}) { + o.values = values +} + +func BoundUnmarshalJSON(o OrderedMapCore, cloneFn CloneFunc, b []byte) error { + if o == nil { + panic("orderedmap: UnmarshalJSON on nil pointer") + } + if o.CoreValues() == nil { + o.CoreSetValues(make(map[string]interface{})) + } + val := o.CoreValues() + err := json.Unmarshal(b, &val) + if err != nil { + return err + } + dec := json.NewDecoder(bytes.NewReader(b)) + if _, err = dec.Token(); err != nil { // skip '{' + return err + } + return decodeOrderedMap(dec, o, cloneFn) +} + +func BoundMarshalJSON(o OrderedMapCore, encoder *json.Encoder, buf io.Writer) error { + buf.Write([]byte{'{'}) + + values := o.CoreValues() + for i, k := range o.CoreKeys() { + if i > 0 { + buf.Write([]byte{','}) + } + // add key + if err := encoder.Encode(k); err != nil { + return err + } + buf.Write([]byte{':'}) + // add value + if err := encoder.Encode(values[k]); err != nil { + return err + } + } + buf.Write([]byte{'}'}) + return nil +} + +func decodeOrderedMap(dec *json.Decoder, o OrderedMapCore, cloneFn CloneFunc) error { + vals := o.CoreValues() + hasKey := make(map[string]bool, len(vals)) + for { + token, err := dec.Token() + if err != nil { + return err + } + if delim, ok := token.(json.Delim); ok && delim == '}' { + return nil + } + key := token.(string) + if hasKey[key] { + // duplicate key + for j, k := range o.CoreKeys() { + if k == key { + copy(o.CoreKeys()[j:], o.CoreKeys()[j+1:]) + break + } + } + o.CoreKeys()[len(o.CoreKeys())-1] = key + } else { + hasKey[key] = true + o.CoreSetKeys(append(o.CoreKeys(), key)) + } + + token, err = dec.Token() + if err != nil { + return err + } + if delim, ok := token.(json.Delim); ok { + switch delim { + case '{': + if values, ok := vals[key].(map[string]interface{}); ok { + newMap := cloneFn(values) + if err = decodeOrderedMap(dec, newMap, cloneFn); err != nil { + return err + } + vals[key] = newMap + } else if oldMap, ok := vals[key].(OrderedMap); ok { + newMap := cloneFn(oldMap.Values()) + if err = decodeOrderedMap(dec, newMap, cloneFn); err != nil { + return err + } + vals[key] = newMap + } else if err = decodeOrderedMap(dec, cloneFn(), cloneFn); err != nil { + return err + } + case '[': + if values, ok := vals[key].([]interface{}); ok { + if err = decodeSlice(dec, values, o, cloneFn); err != nil { + return err + } + } else if err = decodeSlice(dec, []interface{}{}, o, cloneFn); err != nil { + return err + } + } + } + } +} + +func decodeSlice(dec *json.Decoder, s []interface{}, o OrderedMapCore, cloneFn CloneFunc) error { + for index := 0; ; index++ { + token, err := dec.Token() + if err != nil { + return err + } + if delim, ok := token.(json.Delim); ok { + switch delim { + case '{': + if index < len(s) { + if values, ok := s[index].(map[string]interface{}); ok { + newMap := cloneFn(values) + if err = decodeOrderedMap(dec, newMap, cloneFn); err != nil { + return err + } + s[index] = newMap + } else if oldMap, ok := s[index].(OrderedMap); ok { + newMap := cloneFn(oldMap.Values()) + if err = decodeOrderedMap(dec, newMap, cloneFn); err != nil { + return err + } + s[index] = newMap + } else if err = decodeOrderedMap(dec, cloneFn(), cloneFn); err != nil { + return err + } + } else if err = decodeOrderedMap(dec, cloneFn(), cloneFn); err != nil { + return err + } + case '[': + if index < len(s) { + if values, ok := s[index].([]interface{}); ok { + if err = decodeSlice(dec, values, o, cloneFn); err != nil { + return err + } + } else if err = decodeSlice(dec, []interface{}{}, o, cloneFn); err != nil { + return err + } + } else if err = decodeSlice(dec, []interface{}{}, o, cloneFn); err != nil { + return err + } + case ']': + return nil + } + } + } +} diff --git a/orderedmap.go b/orderedmap.go index b463df6..efa0e3c 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -6,288 +6,106 @@ import ( "sort" ) -type Pair struct { - key string - value interface{} -} - -func (kv *Pair) Key() string { - return kv.key -} - -func (kv *Pair) Value() interface{} { - return kv.value -} - -type ByPair struct { - Pairs []*Pair - LessFunc func(a *Pair, j *Pair) bool -} - -func (a ByPair) Len() int { return len(a.Pairs) } -func (a ByPair) Swap(i, j int) { a.Pairs[i], a.Pairs[j] = a.Pairs[j], a.Pairs[i] } -func (a ByPair) Less(i, j int) bool { return a.LessFunc(a.Pairs[i], a.Pairs[j]) } - -type OrderedMapImpl struct { - keys []string - values map[string]interface{} - escapeHTML bool -} - type OrderedMap interface { SetEscapeHTML(on bool) Get(key string) (interface{}, bool) Set(key string, value interface{}) Delete(key string) Keys() []string - SetKeys(keys []string) Values() map[string]interface{} - InitValues() SortKeys(sortFunc func(keys []string)) Sort(lessFunc func(a *Pair, b *Pair) bool) UnmarshalJSON(b []byte) error MarshalJSON() ([]byte, error) - Clone(v ...map[string]interface{}) OrderedMap +} + +type orderedMapFrontend struct { + OrderedMapCore + escapeHTML bool } func New() OrderedMap { - return &OrderedMapImpl{ - keys: make([]string, 0, 1), - values: make(map[string]interface{}, 1), - escapeHTML: true, + return &orderedMapFrontend{ + OrderedMapCore: NewCore(), + escapeHTML: true, } } -func (o *OrderedMapImpl) Clone(oms ...map[string]interface{}) OrderedMap { +func (o *orderedMapFrontend) clone(oms ...map[string]interface{}) *orderedMapFrontend { var om map[string]interface{} if len(oms) > 0 { om = oms[0] } else { om = make(map[string]interface{}) } - return &OrderedMapImpl{ - keys: make([]string, 0, len(om)), - values: om, - escapeHTML: o.escapeHTML, + return &orderedMapFrontend{ + OrderedMapCore: NewCore(om), + escapeHTML: o.escapeHTML, } } -func (o *OrderedMapImpl) SetEscapeHTML(on bool) { +func (o *orderedMapFrontend) SetEscapeHTML(on bool) { o.escapeHTML = on } -func (o *OrderedMapImpl) Get(key string) (interface{}, bool) { - val, exists := o.values[key] - return val, exists -} - -func (o *OrderedMapImpl) Set(key string, value interface{}) { - _, exists := o.values[key] - if !exists { - o.keys = append(o.keys, key) - } - o.values[key] = value -} - -func (o *OrderedMapImpl) Delete(key string) { - // check key is in use - _, ok := o.values[key] - if !ok { - return - } - // remove from keys - for i, k := range o.keys { - if k == key { - o.keys = append(o.keys[:i], o.keys[i+1:]...) - break - } - } - // remove from values - delete(o.values, key) +func (o *orderedMapFrontend) Get(key string) (interface{}, bool) { + return o.OrderedMapCore.CoreGetKey(key) } -func (o *OrderedMapImpl) Keys() []string { - return o.keys +func (o *orderedMapFrontend) Set(key string, value interface{}) { + o.OrderedMapCore.CoreSetKey(key, value) } -func (o *OrderedMapImpl) SetKeys(keys []string) { - o.keys = keys +func (o *orderedMapFrontend) Delete(key string) { + o.OrderedMapCore.CoreDeleteKey(key) } -func (o *OrderedMapImpl) Values() map[string]interface{} { - return o.values +func (o *orderedMapFrontend) Keys() []string { + return o.OrderedMapCore.CoreKeys() } -func (o *OrderedMapImpl) InitValues() { - if o.values == nil { - o.values = make(map[string]interface{}) - } +func (o *orderedMapFrontend) Values() map[string]interface{} { + return o.OrderedMapCore.CoreValues() } // SortKeys Sort the map keys using your sort func -func (o *OrderedMapImpl) SortKeys(sortFunc func(keys []string)) { - sortFunc(o.keys) +func (o *orderedMapFrontend) SortKeys(sortFunc func(keys []string)) { + sortFunc(o.OrderedMapCore.CoreKeys()) } // Sort Sort the map using your sort func -func (o *OrderedMapImpl) Sort(lessFunc func(a *Pair, b *Pair) bool) { - pairs := make([]*Pair, len(o.keys)) - for i, key := range o.keys { - pairs[i] = &Pair{key, o.values[key]} +func (o *orderedMapFrontend) Sort(lessFunc func(a *Pair, b *Pair) bool) { + keys := o.OrderedMapCore.CoreKeys() + values := o.OrderedMapCore.CoreValues() + pairs := make([]*Pair, len(keys)) + for i, key := range keys { + pairs[i] = &Pair{key, values[key]} } - sort.Sort(ByPair{pairs, lessFunc}) - for i, pair := range pairs { - o.keys[i] = pair.key - } -} - -func BoundUnmarshalJSON(o OrderedMap, b []byte) error { - o.InitValues() - val := o.Values() - err := json.Unmarshal(b, &val) - if err != nil { - return err - } - dec := json.NewDecoder(bytes.NewReader(b)) - if _, err = dec.Token(); err != nil { // skip '{' - return err + keys[i] = pair.key } - // o.SetKeys(make([]string, 0, len(o.Values()))) - return decodeOrderedMap(dec, o) } -func (o *OrderedMapImpl) UnmarshalJSON(b []byte) error { - return BoundUnmarshalJSON(o, b) -} - -func decodeOrderedMap(dec *json.Decoder, o OrderedMap) error { - hasKey := make(map[string]bool, len(o.Values())) - for { - token, err := dec.Token() - if err != nil { - return err - } - if delim, ok := token.(json.Delim); ok && delim == '}' { - return nil - } - key := token.(string) - if hasKey[key] { - // duplicate key - for j, k := range o.Keys() { - if k == key { - copy(o.Keys()[j:], o.Keys()[j+1:]) - break - } - } - o.Keys()[len(o.Keys())-1] = key - } else { - hasKey[key] = true - o.SetKeys(append(o.Keys(), key)) - } - - token, err = dec.Token() - if err != nil { - return err - } - if delim, ok := token.(json.Delim); ok { - switch delim { - case '{': - if values, ok := o.Values()[key].(map[string]interface{}); ok { - newMap := o.Clone(values) - if err = decodeOrderedMap(dec, newMap); err != nil { - return err - } - o.Values()[key] = newMap - } else if oldMap, ok := o.Values()[key].(OrderedMap); ok { - newMap := o.Clone(oldMap.Values()) - if err = decodeOrderedMap(dec, newMap); err != nil { - return err - } - o.Values()[key] = newMap - } else if err = decodeOrderedMap(dec, o.Clone()); err != nil { - return err - } - case '[': - if values, ok := o.Values()[key].([]interface{}); ok { - if err = decodeSlice(dec, values, o); err != nil { - return err - } - } else if err = decodeSlice(dec, []interface{}{}, o); err != nil { - return err - } - } - } +func (o *orderedMapFrontend) UnmarshalJSON(b []byte) error { + if o.OrderedMapCore == nil { + // not nice in place of a constructor + out := New().(*orderedMapFrontend) + o.OrderedMapCore = out.OrderedMapCore + o.escapeHTML = out.escapeHTML } + return BoundUnmarshalJSON(o.OrderedMapCore, func(oms ...map[string]interface{}) OrderedMapCore { + clone := o.clone(oms...) + return clone + }, b) } -func decodeSlice(dec *json.Decoder, s []interface{}, o OrderedMap) error { - for index := 0; ; index++ { - token, err := dec.Token() - if err != nil { - return err - } - if delim, ok := token.(json.Delim); ok { - switch delim { - case '{': - if index < len(s) { - if values, ok := s[index].(map[string]interface{}); ok { - newMap := o.Clone(values) - if err = decodeOrderedMap(dec, newMap); err != nil { - return err - } - s[index] = newMap - } else if oldMap, ok := s[index].(OrderedMap); ok { - newMap := o.Clone(oldMap.Values()) - if err = decodeOrderedMap(dec, newMap); err != nil { - return err - } - s[index] = newMap - } else if err = decodeOrderedMap(dec, o.Clone()); err != nil { - return err - } - } else if err = decodeOrderedMap(dec, o.Clone()); err != nil { - return err - } - case '[': - if index < len(s) { - if values, ok := s[index].([]interface{}); ok { - if err = decodeSlice(dec, values, o); err != nil { - return err - } - } else if err = decodeSlice(dec, []interface{}{}, o); err != nil { - return err - } - } else if err = decodeSlice(dec, []interface{}{}, o); err != nil { - return err - } - case ']': - return nil - } - } - } -} - -func (o OrderedMapImpl) MarshalJSON() ([]byte, error) { - var buf bytes.Buffer - buf.WriteByte('{') +func (o orderedMapFrontend) MarshalJSON() ([]byte, error) { + buf := bytes.Buffer{} encoder := json.NewEncoder(&buf) encoder.SetEscapeHTML(o.escapeHTML) - for i, k := range o.keys { - if i > 0 { - buf.WriteByte(',') - } - // add key - if err := encoder.Encode(k); err != nil { - return nil, err - } - buf.WriteByte(':') - // add value - if err := encoder.Encode(o.values[k]); err != nil { - return nil, err - } - } - buf.WriteByte('}') - return buf.Bytes(), nil + err := BoundMarshalJSON(o.OrderedMapCore, encoder, &buf) + // very unlikely side effect: that buf.Bytes must retrieved after + // BoundMarshalJSON returns + return buf.Bytes(), err } diff --git a/orderedmap_test.go b/orderedmap_test.go index b869ecb..d0a3302 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -1,6 +1,7 @@ package orderedmap import ( + "bytes" "encoding/json" "fmt" "reflect" @@ -493,7 +494,7 @@ func TestUnmarshalJSONArrayOfMaps(t *testing.T) { func TestUnmarshalJSONStruct(t *testing.T) { var v struct { - Data OrderedMapImpl `json:"data"` + Data orderedMapFrontend `json:"data"` } err := json.Unmarshal([]byte(`{ "data": { "x": 1 } }`), &v) @@ -641,91 +642,35 @@ func TestMutableAfterUnmarshal(t *testing.T) { } type myType struct { - omap OrderedMap + OrderedMapCore } -func NewMyType() OrderedMap { +func newMyType() *myType { return &myType{ - omap: New(), + OrderedMapCore: NewCore(), } } -func (j *myType) Clone(v ...map[string]interface{}) OrderedMap { - return &myType{ - omap: j.omap.Clone(v...), - } -} - -func (j *myType) Delete(key string) { - j.omap.Delete(key) -} - -func (j *myType) SortKeys(fn func([]string)) { - j.omap.SortKeys(fn) -} -func (j *myType) Sort(fn func(*Pair, *Pair) bool) { - j.omap.Sort(fn) -} - -func (j *myType) SetKeys(keys []string) { - j.omap.SetKeys(keys) -} - -func (j *myType) SetEscapeHTML(bool) { - j.omap.SetEscapeHTML(true) -} - -func (j *myType) InitValues() { - j.omap.InitValues() -} - -// Len implements JSONProperty. -func (j *myType) Len() int { - return len(j.omap.Keys()) -} - -// Get implements JSONProperty. -func (j *myType) Get(key string) (interface{}, bool) { - return j.Lookup(key) -} - -// Lookup implements JSONProperty. -func (j *myType) Lookup(key string) (interface{}, bool) { - out, found := j.omap.Get(key) - if !found { - return nil, false - } - isOmap, found := out.(OrderedMap) - if found { - return &myType{ - omap: isOmap, - }, true - } - return out, true - -} - -// MarshalJSON implements JSONProperty. -func (j *myType) MarshalJSON() ([]byte, error) { - return j.omap.MarshalJSON() -} - -// Set implements JSONProperty. -func (j *myType) Set(key string, value interface{}) { - j.omap.Set(key, value) +func (o *myType) UnmarshalJSON(b []byte) error { + return BoundUnmarshalJSON(o.OrderedMapCore, func(oms ...map[string]interface{}) OrderedMapCore { + clone := newMyType() + return clone + }, b) } -// UnmarshalJSON implements JSONProperty. -func (j *myType) UnmarshalJSON(b []byte) error { - return BoundUnmarshalJSON(j, b) +func (o myType) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + err := BoundMarshalJSON(o.OrderedMapCore, encoder, &buf) + return buf.Bytes(), err } -func (j *myType) Keys() []string { - return j.omap.Keys() +func (o myType) Get(k string) (interface{}, bool) { + return o.OrderedMapCore.CoreGetKey(k) } -func (j *myType) Values() map[string]interface{} { - return j.omap.Values() +func (o *myType) Keys() []string { + return o.OrderedMapCore.CoreKeys() } func TestClone(t *testing.T) { @@ -740,7 +685,7 @@ func TestClone(t *testing.T) { "x": 1 } }` - out := NewMyType() + out := newMyType() err := json.Unmarshal([]byte(input), &out) if err != nil { t.Fatal(err) diff --git a/pair.go b/pair.go new file mode 100644 index 0000000..ab8d40b --- /dev/null +++ b/pair.go @@ -0,0 +1,23 @@ +package orderedmap + +type Pair struct { + key string + value interface{} +} + +func (kv *Pair) Key() string { + return kv.key +} + +func (kv *Pair) Value() interface{} { + return kv.value +} + +type ByPair struct { + Pairs []*Pair + LessFunc func(a *Pair, j *Pair) bool +} + +func (a ByPair) Len() int { return len(a.Pairs) } +func (a ByPair) Swap(i, j int) { a.Pairs[i], a.Pairs[j] = a.Pairs[j], a.Pairs[i] } +func (a ByPair) Less(i, j int) bool { return a.LessFunc(a.Pairs[i], a.Pairs[j]) }