From 82392f2f89fe35bf477b0b1728fab8bb0205ced7 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Tue, 26 Sep 2023 14:41:00 +0200 Subject: [PATCH] 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") + } +}