diff --git a/ast/api_compat.go b/ast/api_compat.go index 6d77a245e..b9cc407b0 100644 --- a/ast/api_compat.go +++ b/ast/api_compat.go @@ -90,7 +90,7 @@ func (self *Node) encodeInterface(buf *[]byte) error { func (self *Parser) getByPath(path ...interface{}) (int, types.ParsingError) { for _, p := range path { if idx, ok := p.(int); ok && idx >= 0 { - if err := self.searchIndex(idx); err != 0 { + if _, err := self.searchIndex(idx); err != 0 { return self.p, err } } else if key, ok := p.(string); ok { diff --git a/ast/parser.go b/ast/parser.go index 5093ec680..f049e3142 100644 --- a/ast/parser.go +++ b/ast/parser.go @@ -391,21 +391,22 @@ func (self *Parser) objectEnd() (bool, types.ParsingError) { func (self *Parser) searchKey(match string) (int, types.ParsingError) { ns := len(self.s) - keyPos := -1 + comma := -1 if err := self.object(); err != 0 { - return keyPos, err + return comma, err } /* check for EOF */ if self.p = self.lspace(self.p); self.p >= ns { - return keyPos, types.ERR_EOF + return comma, types.ERR_EOF } /* check for empty object */ if self.s[self.p] == '}' { - return keyPos, _ERR_NOT_FOUND + return comma, _ERR_NOT_FOUND } + comma = self.p var njs types.JsonState var err types.ParsingError @@ -415,49 +416,49 @@ func (self *Parser) searchKey(match string) (int, types.ParsingError) { /* decode the key */ if njs = self.decodeValue(); njs.Vt != types.V_STRING { - return keyPos, types.ERR_INVALID_CHAR + return comma, types.ERR_INVALID_CHAR } /* extract the key */ idx := self.p - 1 key := self.s[njs.Iv:idx] - keyPos = int(njs.Iv - 1) /* check for escape sequence */ if njs.Ep != -1 { if key, err = unquote(key); err != 0 { - return keyPos, err + return comma, err } } /* expect a ':' delimiter */ if err = self.delim(); err != 0 { - return keyPos, err + return comma, err } /* skip value */ if key != match { if _, err = self.skipFast(); err != 0 { - return keyPos, err + return comma, err } } else { - return keyPos, 0 + return comma, 0 } /* check for EOF */ self.p = self.lspace(self.p) if self.p >= ns { - return keyPos, types.ERR_EOF + return comma, types.ERR_EOF } /* check for the next character */ switch self.s[self.p] { case ',': + comma = self.p self.p++ case '}': - return keyPos, _ERR_NOT_FOUND + return comma, _ERR_NOT_FOUND default: - return keyPos, types.ERR_INVALID_CHAR + return comma, types.ERR_INVALID_CHAR } } } @@ -502,21 +503,23 @@ func (self *Parser) arrayEnd() (bool, types.ParsingError) { } } -func (self *Parser) searchIndex(idx int) types.ParsingError { +func (self *Parser) searchIndex(idx int) (int, types.ParsingError) { + comma := -1 ns := len(self.s) if err := self.array(); err != 0 { - return err + return comma, err } /* check for EOF */ if self.p = self.lspace(self.p); self.p >= ns { - return types.ERR_EOF + return comma, types.ERR_EOF } /* check for empty array */ if self.s[self.p] == ']' { - return _ERR_NOT_FOUND + return comma, _ERR_NOT_FOUND } + comma = self.p var err types.ParsingError /* allocate array space and parse every element */ @@ -524,27 +527,28 @@ func (self *Parser) searchIndex(idx int) types.ParsingError { /* decode the value */ if _, err = self.skipFast(); err != 0 { - return err + return comma, err } /* check for EOF */ self.p = self.lspace(self.p) if self.p >= ns { - return types.ERR_EOF + return comma, types.ERR_EOF } /* check for the next character */ switch self.s[self.p] { case ',': + comma = self.p self.p++ case ']': - return _ERR_NOT_FOUND + return comma, _ERR_NOT_FOUND default: - return types.ERR_INVALID_CHAR + return comma, types.ERR_INVALID_CHAR } } - return 0 + return comma, 0 } func (self *Node) skipNextNode() *Node { diff --git a/ast/raw.go b/ast/raw.go index 0920bf60b..91e287fa5 100644 --- a/ast/raw.go +++ b/ast/raw.go @@ -154,14 +154,14 @@ func (self Value) GetByPath(path ...interface{}) Value { return rawNode(self.js[s:p.p]) } -// GetByPath load given path on demands, -// which only ensure nodes before this path got parsed -func (self *Value) SetByPath(val Value, path ...interface{}) error { +// SetByPath set value on given path and create nodes on the json if not exist +func (self *Value) SetByPath(val Value, path ...interface{}) (bool, error) { + exist := false if self.Check() != nil { - return self + return exist, self } if val.Check() != nil { - return val + return exist, val } p := NewParserObj(self.js) @@ -170,9 +170,9 @@ func (self *Value) SetByPath(val Value, path ...interface{}) error { for i, k := range path { if id, ok := k.(int); ok && id >= 0 { - if err = p.searchIndex(id); err != 0 { + if _, err = p.searchIndex(id); err != 0 { if err != _ERR_NOT_FOUND { - return errRawNode(p.ExportError(err)) + return exist, errRawNode(p.ExportError(err)) } idx = i break @@ -180,7 +180,7 @@ func (self *Value) SetByPath(val Value, path ...interface{}) error { } else if key, ok := k.(string); ok { if _, err = p.searchKey(key); err != 0 { if err != _ERR_NOT_FOUND { - return errRawNode(p.ExportError(err)) + return exist, errRawNode(p.ExportError(err)) } idx = i break @@ -192,10 +192,11 @@ func (self *Value) SetByPath(val Value, path ...interface{}) error { var b []byte if err == 0 { + exist = true // found, just skip and replace - s, err := p.skip() + s, err := p.skipFast() if err != 0 { - return errRawNode(p.ExportError(err)) + return exist, errRawNode(p.ExportError(err)) } size := len(self.js) + len(val.js) - (p.p-s) b = make([]byte, 0, size) @@ -213,16 +214,17 @@ func (self *Value) SetByPath(val Value, path ...interface{}) error { if !empty { b = append(b, ","...) } + // creat new nodes on path b = appendPathValue(b, path[idx:], val) } b = append(b, self.js[p.p:]...) self.js = rt.Mem2Str(b) - return nil + return exist, nil } -// [2,"a"] - 1 => {"a":1} -// ["a",2] - 1 => "a":[1] +// [2,"a"],1 => {"a":1} +// ["a",2],1 => "a":[1] func appendPathValue(b []byte, path []interface{}, val Value) []byte { for i, k := range path { if key, ok := k.(string); ok { @@ -253,6 +255,64 @@ func appendPathValue(b []byte, path []interface{}, val Value) []byte { return b } +// UnsetByPath delete value on given path. +func (self *Value) UnsetByPath(path ...interface{}) (bool, error) { + if self.Check() != nil { + return false, self + } + + p := NewParserObj(self.js) + var err types.ParsingError + var comma = -1 + + for _, k := range path { + if id, ok := k.(int); ok && id >= 0 { + if comma, err = p.searchIndex(id); err != 0 { + if err == _ERR_NOT_FOUND { + return false, nil + } + return false, p.ExportError(err) + } + } else if key, ok := k.(string); ok { + if comma, err = p.searchKey(key); err != 0 { + if err == _ERR_NOT_FOUND { + return false, nil + } + return false, p.ExportError(err) + } + } else { + panic("path must be either int(>=0) or string") + } + } + + var b []byte + s, err := p.skipFast() + if err != 0 { + return true, errRawNode(p.ExportError(err)) + } + if comma != -1 { + s = comma + } + + e := p.p + println(string(self.js[s])) + if self.js[s] != ',' { // first elem + // check if trailling ',' + p.p = p.lspace(p.p) + println(string(self.js[p.p])) + if p.p < len(self.js) && self.js[p.p] == ',' { + e = p.p+1 + } + } + + size := len(self.js) - (e-s) + b = make([]byte, 0, size) + b = append(b, self.js[:s]...) + b = append(b, self.js[e:]...) + self.js = rt.Mem2Str(b) + return true, nil +} + // Get loads given key of an object node on demands func (self Value) Get(key string) Value { if self.Check() != nil { @@ -437,7 +497,8 @@ func (ps points) Len() int { return len(ps) } -// Set sets the node of given key under self, and reports if the key has existed. +// Set sets the node of given key under self, and insert new value if not exist. +// It reports if the key has existed. func (self *Value) Set(key string, val Value) (bool, error) { return self.SetMany([]string{key}, []Value{val}) } @@ -534,8 +595,9 @@ func (self *Value) SetMany(keys []string, vals []Value) (bool, error) { return exist, nil } -// SetByIndex sets the node of given index, and reports if the key has existed. +// SetByIndex sets the node of given index and insert new value if not exist. // If the index is out range of self's children, it will be ADD to the last +// and reports if the key has existed. func (self *Value) SetByIndex(id int, val Value) (bool, error) { return self.SetManyByIndex([]int{id}, []Value{val}) } diff --git a/ast/raw_test.go b/ast/raw_test.go index 79dd93019..dcb2a899a 100644 --- a/ast/raw_test.go +++ b/ast/raw_test.go @@ -323,25 +323,68 @@ func TestRawNode_SetByPath(t *testing.T) { js string paths []interface{} val Value + exist bool err string out string }{ - {"exist object",`{"a":1}`,[]interface{}{"a"},NewValue(`2`),"",`{"a":2}`}, - {"not-exist object",`{"b":1}`,[]interface{}{"a"},NewValue(`2`),"",`{"b":1,"a":2}`}, - {"empty object",`{}`,[]interface{}{"a"},NewValue(`2`),"",`{"a":2}`}, - {"empty object 2",`{}`,[]interface{}{"a",1},NewValue(`2`),"",`{"a":[2]}`}, - {"empty object 3",`{}`,[]interface{}{"a",1,"a"},NewValue(`2`),"",`{"a":[{"a":2}]}`}, - {"exist array",`[1]`,[]interface{}{0},NewValue(`2`),"",`[2]`}, - {"not exist array",`[1]`,[]interface{}{1},NewValue(`2`),"",`[1,2]`}, - {"empty array",`[]`,[]interface{}{1},NewValue(`2`),"",`[2]`}, - {"empty array 2",`[]`,[]interface{}{1,1},NewValue(`2`),"",`[[2]]`}, - {"empty array 3",`[]`,[]interface{}{1,"a",1},NewValue(`2`),"",`[{"a":[2]}]`}, - {"empty array 3",`[]`,[]interface{}{1,"a","a"},NewValue(`2`),"",`[{"a":{"a":2}}]`}, + {"exist object",`{"a":1}`,[]interface{}{"a"},NewValue(`2`),true,"",`{"a":2}`}, + {"not-exist object",`{"b":1}`,[]interface{}{"a"},NewValue(`2`),false,"",`{"b":1,"a":2}`}, + {"empty object",`{}`,[]interface{}{"a"},NewValue(`2`),false,"",`{"a":2}`}, + {"empty object 2",`{}`,[]interface{}{"a",1},NewValue(`2`),false,"",`{"a":[2]}`}, + {"empty object 3",`{}`,[]interface{}{"a",1,"a"},NewValue(`2`),false,"",`{"a":[{"a":2}]}`}, + {"exist array",`[1]`,[]interface{}{0},NewValue(`2`),true,"",`[2]`}, + {"not exist array",`[1]`,[]interface{}{1},NewValue(`2`),false,"",`[1,2]`}, + {"empty array",`[]`,[]interface{}{1},NewValue(`2`),false,"",`[2]`}, + {"empty array 2",`[]`,[]interface{}{1,1},NewValue(`2`),false,"",`[[2]]`}, + {"empty array 3",`[]`,[]interface{}{1,"a",1},NewValue(`2`),false,"",`[{"a":[2]}]`}, + {"empty array 3",`[]`,[]interface{}{1,"a","a"},NewValue(`2`),false,"",`[{"a":{"a":2}}]`}, + } + for _, c := range tests { + println(c.name) + root := NewValue(c.js) + exist, err := root.SetByPath(c.val, c.paths...) + if err != nil && err.Error() != c.err { + t.Fatal(err) + } + if out := root.Raw(); err != nil { + t.Fatal() + } else { + require.Equal(t, c.out, out) + } + require.Equal(t, c.exist, exist) + } +} + +func TestRawNode_UnsetByPath(t *testing.T) { + tests := []struct{ + name string + js string + paths []interface{} + exist bool + err string + out string + }{ + {"exist object",`{"a":1}`,[]interface{}{"a"},true,"",`{}`}, + {"exist object",`{"a":1,"b":2,"c":3}`,[]interface{}{"a"},true,"",`{"b":2,"c":3}`}, + {"exist object",`{"a":1,"b":2,"c":3}`,[]interface{}{"b"},true,"",`{"a":1,"c":3}`}, + {"exist object",`{"a":1,"b":2,"c":3}`,[]interface{}{"c"},true,"",`{"a":1,"b":2}`}, + {"not-exist object",`{"b":1}`,[]interface{}{"a"},false,"",`{"b":1}`}, + {"empty object",`{}`,[]interface{}{"a"},false,"",`{}`}, + {"exist object 2",`{"a":{"a":1}}`,[]interface{}{"a","a"},true,"",`{"a":{}}`}, + {"exist object 2",`[{"a":1}]`,[]interface{}{0,"a"},true,"",`[{}]`}, + + {"not exist array",`[1]`,[]interface{}{1},false,"",`[1]`}, + {"empty array",`[]`,[]interface{}{1},false,"",`[]`}, + {"exist array",`[[1,2,3]]`,[]interface{}{0,0},true,"",`[[2,3]]`}, + {"exist array",`[[1,2,3]]`,[]interface{}{0,1},true,"",`[[1,3]]`}, + {"exist array",`[[1,2,3]]`,[]interface{}{0,2},true,"",`[[1,2]]`}, + {"exist array 2",`[[1]]`,[]interface{}{0,0},true,"",`[[]]`}, + {"exist array 2",`{"a":[1]}`,[]interface{}{"a",0},true,"",`{"a":[]}`}, } for _, c := range tests { println(c.name) root := NewValue(c.js) - err := root.SetByPath(c.val, c.paths...) + exist, err := root.UnsetByPath(c.paths...) if err != nil && err.Error() != c.err { t.Fatal(err) } @@ -350,6 +393,7 @@ func TestRawNode_SetByPath(t *testing.T) { } else { require.Equal(t, c.out, out) } + require.Equal(t, c.exist, exist) } }