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 61587d9..efa0e3c 100644 --- a/orderedmap.go +++ b/orderedmap.go @@ -6,261 +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 +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) +} + +type orderedMapFrontend struct { + OrderedMapCore + escapeHTML 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 OrderedMap struct { - keys []string - values map[string]interface{} - escapeHTML bool +func New() OrderedMap { + return &orderedMapFrontend{ + OrderedMapCore: NewCore(), + escapeHTML: true, + } } -func New() *OrderedMap { - o := OrderedMap{} - o.keys = []string{} - o.values = map[string]interface{}{} - o.escapeHTML = true - return &o +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 &orderedMapFrontend{ + OrderedMapCore: NewCore(om), + escapeHTML: o.escapeHTML, + } } -func (o *OrderedMap) SetEscapeHTML(on bool) { +func (o *orderedMapFrontend) SetEscapeHTML(on bool) { o.escapeHTML = on } -func (o *OrderedMap) Get(key string) (interface{}, bool) { - val, exists := o.values[key] - return val, exists +func (o *orderedMapFrontend) Get(key string) (interface{}, bool) { + return o.OrderedMapCore.CoreGetKey(key) } -func (o *OrderedMap) Set(key string, value interface{}) { - _, exists := o.values[key] - if !exists { - o.keys = append(o.keys, key) - } - o.values[key] = value +func (o *orderedMapFrontend) Set(key string, value interface{}) { + o.OrderedMapCore.CoreSetKey(key, value) } -func (o *OrderedMap) 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) Delete(key string) { + o.OrderedMapCore.CoreDeleteKey(key) } -func (o *OrderedMap) Keys() []string { - return o.keys +func (o *orderedMapFrontend) Keys() []string { + return o.OrderedMapCore.CoreKeys() } -func (o *OrderedMap) Values() map[string]interface{} { - return o.values +func (o *orderedMapFrontend) Values() map[string]interface{} { + return o.OrderedMapCore.CoreValues() } // SortKeys Sort the map keys using your sort func -func (o *OrderedMap) 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 *OrderedMap) 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 + keys[i] = pair.key } } -func (o *OrderedMap) UnmarshalJSON(b []byte) error { - if o.values == nil { - o.values = map[string]interface{}{} - } - err := json.Unmarshal(b, &o.values) - if 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 } - dec := json.NewDecoder(bytes.NewReader(b)) - if _, err = dec.Token(); err != nil { // skip '{' - return err - } - o.keys = make([]string, 0, len(o.values)) - return decodeOrderedMap(dec, o) + return BoundUnmarshalJSON(o.OrderedMapCore, func(oms ...map[string]interface{}) OrderedMapCore { + clone := o.clone(oms...) + return clone + }, 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.keys = 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 := OrderedMap{ - keys: make([]string, 0, len(values)), - values: values, - escapeHTML: o.escapeHTML, - } - if err = decodeOrderedMap(dec, &newMap); err != nil { - return err - } - o.values[key] = newMap - } else if oldMap, ok := o.values[key].(OrderedMap); ok { - newMap := OrderedMap{ - keys: make([]string, 0, len(oldMap.values)), - values: oldMap.values, - escapeHTML: o.escapeHTML, - } - if err = decodeOrderedMap(dec, &newMap); err != nil { - return err - } - o.values[key] = newMap - } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { - return err - } - case '[': - if values, ok := o.values[key].([]interface{}); ok { - if err = decodeSlice(dec, values, o.escapeHTML); err != nil { - return err - } - } else if err = decodeSlice(dec, []interface{}{}, o.escapeHTML); err != nil { - return err - } - } - } - } -} - -func decodeSlice(dec *json.Decoder, s []interface{}, escapeHTML bool) 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 := OrderedMap{ - keys: make([]string, 0, len(values)), - values: values, - escapeHTML: escapeHTML, - } - if err = decodeOrderedMap(dec, &newMap); err != nil { - return err - } - s[index] = newMap - } else if oldMap, ok := s[index].(OrderedMap); ok { - newMap := OrderedMap{ - keys: make([]string, 0, len(oldMap.values)), - values: oldMap.values, - escapeHTML: escapeHTML, - } - if err = decodeOrderedMap(dec, &newMap); err != nil { - return err - } - s[index] = newMap - } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { - return err - } - } else if err = decodeOrderedMap(dec, &OrderedMap{}); err != nil { - return err - } - case '[': - if index < len(s) { - if values, ok := s[index].([]interface{}); ok { - if err = decodeSlice(dec, values, escapeHTML); err != nil { - return err - } - } else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil { - return err - } - } else if err = decodeSlice(dec, []interface{}{}, escapeHTML); err != nil { - return err - } - case ']': - return nil - } - } - } -} - -func (o OrderedMap) 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 5b89ef6..d0a3302 100644 --- a/orderedmap_test.go +++ b/orderedmap_test.go @@ -1,6 +1,7 @@ package orderedmap import ( + "bytes" "encoding/json" "fmt" "reflect" @@ -69,10 +70,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 +494,7 @@ func TestUnmarshalJSONArrayOfMaps(t *testing.T) { func TestUnmarshalJSONStruct(t *testing.T) { var v struct { - Data *OrderedMap `json:"data"` + Data orderedMapFrontend `json:"data"` } err := json.Unmarshal([]byte(`{ "data": { "x": 1 } }`), &v) @@ -595,3 +596,143 @@ 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") + } +} + +type myType struct { + OrderedMapCore +} + +func newMyType() *myType { + return &myType{ + OrderedMapCore: NewCore(), + } +} + +func (o *myType) UnmarshalJSON(b []byte) error { + return BoundUnmarshalJSON(o.OrderedMapCore, func(oms ...map[string]interface{}) OrderedMapCore { + clone := newMyType() + return clone + }, 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 (o myType) Get(k string) (interface{}, bool) { + return o.OrderedMapCore.CoreGetKey(k) +} + +func (o *myType) Keys() []string { + return o.OrderedMapCore.CoreKeys() +} + +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") + } +} 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]) }