From 82392f2f89fe35bf477b0b1728fab8bb0205ced7 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Tue, 26 Sep 2023 14:41:00 +0200 Subject: [PATCH 1/3] Fixed enable the mutation of a unmarshaled json. In the decode methods the nested orderedMaps had not been pointer with prevents the mutation. To the fix broke some tests the cast to x.(OrderedMap) failed. To Fix this the OrderedMap is now an interface. This broke the TestUnmarshalJSONStruct test to fix the export of OrderedMapImpl is need. This change is a breaking! --- orderedmap.go | 67 +++++++++++++++++++++++++++------------------- orderedmap_test.go | 52 ++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/orderedmap.go b/orderedmap.go index 61587d9..a951421 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -28,30 +28,43 @@ 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 OrderedMap struct { +type OrderedMapImpl struct { keys []string values map[string]interface{} escapeHTML bool } -func New() *OrderedMap { - o := OrderedMap{} +type OrderedMap interface { + SetEscapeHTML(on bool) + Get(key string) (interface{}, bool) + Set(key string, value interface{}) + Delete(key string) + Keys() []string + Values() map[string]interface{} + SortKeys(sortFunc func(keys []string)) + Sort(lessFunc func(a *Pair, b *Pair) bool) + UnmarshalJSON(b []byte) error + MarshalJSON() ([]byte, error) +} + +func New() OrderedMap { + o := OrderedMapImpl{} o.keys = []string{} o.values = map[string]interface{}{} o.escapeHTML = true return &o } -func (o *OrderedMap) SetEscapeHTML(on bool) { +func (o *OrderedMapImpl) SetEscapeHTML(on bool) { o.escapeHTML = on } -func (o *OrderedMap) Get(key string) (interface{}, bool) { +func (o *OrderedMapImpl) Get(key string) (interface{}, bool) { val, exists := o.values[key] return val, exists } -func (o *OrderedMap) Set(key string, value interface{}) { +func (o *OrderedMapImpl) Set(key string, value interface{}) { _, exists := o.values[key] if !exists { o.keys = append(o.keys, key) @@ -59,7 +72,7 @@ func (o *OrderedMap) Set(key string, value interface{}) { o.values[key] = value } -func (o *OrderedMap) Delete(key string) { +func (o *OrderedMapImpl) Delete(key string) { // check key is in use _, ok := o.values[key] if !ok { @@ -76,21 +89,21 @@ func (o *OrderedMap) Delete(key string) { delete(o.values, key) } -func (o *OrderedMap) Keys() []string { +func (o *OrderedMapImpl) Keys() []string { return o.keys } -func (o *OrderedMap) Values() map[string]interface{} { +func (o *OrderedMapImpl) Values() map[string]interface{} { return o.values } // SortKeys Sort the map keys using your sort func -func (o *OrderedMap) SortKeys(sortFunc func(keys []string)) { +func (o *OrderedMapImpl) SortKeys(sortFunc func(keys []string)) { sortFunc(o.keys) } // Sort Sort the map using your sort func -func (o *OrderedMap) Sort(lessFunc func(a *Pair, b *Pair) bool) { +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]} @@ -103,7 +116,7 @@ func (o *OrderedMap) Sort(lessFunc func(a *Pair, b *Pair) bool) { } } -func (o *OrderedMap) UnmarshalJSON(b []byte) error { +func (o *OrderedMapImpl) UnmarshalJSON(b []byte) error { if o.values == nil { o.values = map[string]interface{}{} } @@ -119,7 +132,7 @@ func (o *OrderedMap) UnmarshalJSON(b []byte) error { return decodeOrderedMap(dec, o) } -func decodeOrderedMap(dec *json.Decoder, o *OrderedMap) error { +func decodeOrderedMap(dec *json.Decoder, o *OrderedMapImpl) error { hasKey := make(map[string]bool, len(o.values)) for { token, err := dec.Token() @@ -152,26 +165,26 @@ func decodeOrderedMap(dec *json.Decoder, o *OrderedMap) error { switch delim { case '{': if values, ok := o.values[key].(map[string]interface{}); ok { - newMap := OrderedMap{ + newMap := &OrderedMapImpl{ keys: make([]string, 0, len(values)), values: values, escapeHTML: o.escapeHTML, } - if err = decodeOrderedMap(dec, &newMap); err != nil { + if err = decodeOrderedMap(dec, newMap); err != nil { return err } o.values[key] = newMap - } else if oldMap, ok := o.values[key].(OrderedMap); ok { - newMap := OrderedMap{ + } else if oldMap, ok := o.values[key].(*OrderedMapImpl); ok { + newMap := &OrderedMapImpl{ keys: make([]string, 0, len(oldMap.values)), values: oldMap.values, escapeHTML: o.escapeHTML, } - if err = decodeOrderedMap(dec, &newMap); err != nil { + if err = decodeOrderedMap(dec, newMap); err != nil { return err } o.values[key] = newMap - } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { + } else if err = decodeOrderedMap(dec, &OrderedMapImpl{}); err != nil { return err } case '[': @@ -198,29 +211,29 @@ func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) error { case '{': if index < len(s) { if values, ok := s[index].(map[string]interface{}); ok { - newMap := OrderedMap{ + newMap := &OrderedMapImpl{ keys: make([]string, 0, len(values)), values: values, escapeHTML: escapeHTML, } - if err = decodeOrderedMap(dec, &newMap); err != nil { + if err = decodeOrderedMap(dec, newMap); err != nil { return err } s[index] = newMap - } else if oldMap, ok := s[index].(OrderedMap); ok { - newMap := OrderedMap{ + } else if oldMap, ok := s[index].(*OrderedMapImpl); ok { + newMap := &OrderedMapImpl{ keys: make([]string, 0, len(oldMap.values)), values: oldMap.values, escapeHTML: escapeHTML, } - if err = decodeOrderedMap(dec, &newMap); err != nil { + if err = decodeOrderedMap(dec, newMap); err != nil { return err } s[index] = newMap - } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { + } else if err = decodeOrderedMap(dec, &OrderedMapImpl{}); err != nil { return err } - } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { + } else if err = decodeOrderedMap(dec, &OrderedMapImpl{}); err != nil { return err } case '[': @@ -242,7 +255,7 @@ func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) error { } } -func (o OrderedMap) MarshalJSON() ([]byte, error) { +func (o OrderedMapImpl) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.WriteByte('{') encoder := json.NewEncoder(&buf) diff --git a/orderedmap_test.go b/orderedmap_test.go index 5b89ef6..278c5b8 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -69,10 +69,10 @@ func TestOrderedMap(t *testing.T) { // Values method values := o.Values() expectedValues := map[string]interface{}{ - "number": 4, - "string": "x", + "number": 4, + "string": "x", "strings": []string{"t", "u"}, - "mixed": []interface{}{ 1, "1" }, + "mixed": []interface{}{1, "1"}, } if !reflect.DeepEqual(values, expectedValues) { t.Error("Values method returned unexpected map") @@ -493,7 +493,7 @@ func TestUnmarshalJSONArrayOfMaps(t *testing.T) { func TestUnmarshalJSONStruct(t *testing.T) { var v struct { - Data *OrderedMap `json:"data"` + Data OrderedMapImpl `json:"data"` } err := json.Unmarshal([]byte(`{ "data": { "x": 1 } }`), &v) @@ -595,3 +595,47 @@ func TestOrderedMap_empty_map(t *testing.T) { t.Error("Got", marshalledStr) } } + +func TestMutableAfterUnmarshal(t *testing.T) { + const input = `{ + "foo": "bar", + "prop": { + "v1": 1, + "v2": "v1" + } +}` + out := New() + err := json.Unmarshal([]byte(input), &out) + if err != nil { + t.Fatal(err) + } + + iout, _ := out.Get("prop") + iout.(OrderedMap).Set("v3", "blabla") + + if out.Keys()[0] != "foo" { + t.Fatal("key order changed") + } + if out.Keys()[1] != "prop" { + t.Fatal("key order changed") + } + inPropX, _ := out.Get("prop") + inProp := inPropX.(OrderedMap) + + v1, _ := inProp.Get("v1") + if inProp.Keys()[0] != "v1" || v1.(float64) != 1 { + t.Fatal("key order changed") + } + v2, _ := inProp.Get("v2") + if inProp.Keys()[1] != "v2" || v2.(string) != "v1" { + t.Fatal("key order changed") + } + + if inProp.Keys()[2] != "v3" { + t.Fatal("key order changed") + } + v3, _ := inProp.Get("v3") + if v3.(string) != "blabla" { + t.Fatal("expect blabla") + } +} From d7ddbd25c6a5f16096145b2daa251c2a8b37fcef Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Mon, 30 Oct 2023 03:23:59 +0100 Subject: [PATCH 2/3] Added the functionality to deserialize orderedmaps into CustomTypes like `JSDict`. I added these method to my previous added OrderedMap interface: - SetKeys(keys []string) - InitValues() - Clone(v ...map[string]interface{}) OrderedMap --- orderedmap.go | 118 +++++++++++++++++++---------------- orderedmap_test.go | 152 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 52 deletions(-) diff --git a/orderedmap.go b/orderedmap.go index a951421..b463df6 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -40,19 +40,36 @@ type OrderedMap interface { 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 } func New() OrderedMap { - o := OrderedMapImpl{} - o.keys = []string{} - o.values = map[string]interface{}{} - o.escapeHTML = true - return &o + return &OrderedMapImpl{ + keys: make([]string, 0, 1), + values: make(map[string]interface{}, 1), + escapeHTML: true, + } +} + +func (o *OrderedMapImpl) Clone(oms ...map[string]interface{}) OrderedMap { + 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, + } } func (o *OrderedMapImpl) SetEscapeHTML(on bool) { @@ -93,10 +110,20 @@ func (o *OrderedMapImpl) Keys() []string { return o.keys } +func (o *OrderedMapImpl) SetKeys(keys []string) { + o.keys = keys +} + func (o *OrderedMapImpl) Values() map[string]interface{} { return o.values } +func (o *OrderedMapImpl) InitValues() { + if o.values == nil { + o.values = make(map[string]interface{}) + } +} + // SortKeys Sort the map keys using your sort func func (o *OrderedMapImpl) SortKeys(sortFunc func(keys []string)) { sortFunc(o.keys) @@ -116,11 +143,10 @@ func (o *OrderedMapImpl) Sort(lessFunc func(a *Pair, b *Pair) bool) { } } -func (o *OrderedMapImpl) UnmarshalJSON(b []byte) error { - if o.values == nil { - o.values = map[string]interface{}{} - } - err := json.Unmarshal(b, &o.values) +func BoundUnmarshalJSON(o OrderedMap, b []byte) error { + o.InitValues() + val := o.Values() + err := json.Unmarshal(b, &val) if err != nil { return err } @@ -128,12 +154,16 @@ func (o *OrderedMapImpl) UnmarshalJSON(b []byte) error { if _, err = dec.Token(); err != nil { // skip '{' return err } - o.keys = make([]string, 0, len(o.values)) + // o.SetKeys(make([]string, 0, len(o.Values()))) return decodeOrderedMap(dec, o) } -func decodeOrderedMap(dec *json.Decoder, o *OrderedMapImpl) error { - hasKey := make(map[string]bool, len(o.values)) +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 { @@ -145,16 +175,16 @@ func decodeOrderedMap(dec *json.Decoder, o *OrderedMapImpl) error { key := token.(string) if hasKey[key] { // duplicate key - for j, k := range o.keys { + for j, k := range o.Keys() { if k == key { - copy(o.keys[j:], o.keys[j+1:]) + copy(o.Keys()[j:], o.Keys()[j+1:]) break } } - o.keys[len(o.keys)-1] = key + o.Keys()[len(o.Keys())-1] = key } else { hasKey[key] = true - o.keys = append(o.keys, key) + o.SetKeys(append(o.Keys(), key)) } token, err = dec.Token() @@ -164,35 +194,27 @@ func decodeOrderedMap(dec *json.Decoder, o *OrderedMapImpl) error { if delim, ok := token.(json.Delim); ok { switch delim { case '{': - if values, ok := o.values[key].(map[string]interface{}); ok { - newMap := &OrderedMapImpl{ - keys: make([]string, 0, len(values)), - values: values, - escapeHTML: o.escapeHTML, - } + 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].(*OrderedMapImpl); ok { - newMap := &OrderedMapImpl{ - keys: make([]string, 0, len(oldMap.values)), - values: oldMap.values, - escapeHTML: o.escapeHTML, - } + 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, &OrderedMapImpl{}); err != nil { + 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.escapeHTML); err != nil { + 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.escapeHTML); err != nil { + } else if err = decodeSlice(dec, []interface{}{}, o); err != nil { return err } } @@ -200,7 +222,7 @@ func decodeOrderedMap(dec *json.Decoder, o *OrderedMapImpl) error { } } -func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) error { +func decodeSlice(dec *json.Decoder, s []interface{}, o OrderedMap) error { for index := 0; ; index++ { token, err := dec.Token() if err != nil { @@ -211,41 +233,33 @@ func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) error { case '{': if index < len(s) { if values, ok := s[index].(map[string]interface{}); ok { - newMap := &OrderedMapImpl{ - keys: make([]string, 0, len(values)), - values: values, - escapeHTML: escapeHTML, - } + newMap := o.Clone(values) if err = decodeOrderedMap(dec, newMap); err != nil { return err } s[index] = newMap - } else if oldMap, ok := s[index].(*OrderedMapImpl); ok { - newMap := &OrderedMapImpl{ - keys: make([]string, 0, len(oldMap.values)), - values: oldMap.values, - escapeHTML: escapeHTML, - } + } 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, &OrderedMapImpl{}); err != nil { + } else if err = decodeOrderedMap(dec, o.Clone()); err != nil { return err } - } else if err = decodeOrderedMap(dec, &OrderedMapImpl{}); err != nil { + } 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, escapeHTML); err != nil { + if err = decodeSlice(dec, values, o); err != nil { return err } - } else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil { + } else if err = decodeSlice(dec, []interface{}{}, o); err != nil { return err } - } else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil { + } else if err = decodeSlice(dec, []interface{}{}, o); err != nil { return err } case ']': diff --git a/orderedmap_test.go b/orderedmap_test.go index 278c5b8..b869ecb 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -639,3 +639,155 @@ func TestMutableAfterUnmarshal(t *testing.T) { t.Fatal("expect blabla") } } + +type myType struct { + omap OrderedMap +} + +func NewMyType() OrderedMap { + return &myType{ + omap: New(), + } +} + +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) +} + +// UnmarshalJSON implements JSONProperty. +func (j *myType) UnmarshalJSON(b []byte) error { + return BoundUnmarshalJSON(j, b) +} + +func (j *myType) Keys() []string { + return j.omap.Keys() +} + +func (j *myType) Values() map[string]interface{} { + return j.omap.Values() +} + +func TestClone(t *testing.T) { + const input = `{ + "foo": [ + { "x": 1 }, + { "y": 2 }, + "string", + 4711 + ], + "bar": { + "x": 1 + } + }` + out := NewMyType() + err := json.Unmarshal([]byte(input), &out) + if err != nil { + t.Fatal(err) + } + + oarray, found := out.Get("foo") + if !found { + t.Fatal("foo is not found") + } + array, found := oarray.([]interface{}) + if !found { + t.Fatal("foo is not an array") + } + + if len(array) != 4 { + t.Fatal("array has not 4 elements") + } + _, found = array[0].(*myType) + if !found { + t.Fatal("array[0] is not a myType") + } + _, found = array[1].(*myType) + if !found { + t.Fatal("array[1] is not a myType") + } + if array[2].(string) != "string" { + t.Fatal("array[2] is not a string") + } + if array[3].(float64) != 4711 { + t.Fatal("array[3] is not a float64") + } + if array[0].(*myType).Keys()[0] != "x" { + t.Fatal("array[0].x is not 1") + } + if array[1].(*myType).Keys()[0] != "y" { + t.Fatal("array[1].y is not 2") + } + + obar, found := out.Get("bar") + if !found { + t.Fatal("bar is not found") + } + bar, found := obar.(*myType) + if !found { + t.Fatal("bar is not a myType") + } + if bar.Keys()[0] != "x" { + t.Fatal("bar.x is not 1") + } +} From 280d4a8d6fe435ef9b67a743fd196c1d2e29e0a8 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Mon, 30 Oct 2023 13:01:43 +0100 Subject: [PATCH 3/3] 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]) }