github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zjson/set.go (about)

     1  package zjson
     2  
     3  import (
     4  	jsong "encoding/json"
     5  	"errors"
     6  	"strconv"
     7  
     8  	"github.com/sohaha/zlsgo/zstring"
     9  )
    10  
    11  type (
    12  	Options struct {
    13  		Optimistic     bool
    14  		ReplaceInPlace bool
    15  	}
    16  	dtype      struct{}
    17  	pathResult struct {
    18  		part  string
    19  		gpart string
    20  		path  string
    21  		force bool
    22  		more  bool
    23  	}
    24  )
    25  
    26  func Stringify(value interface{}) (json string) {
    27  	if jsonByte, err := jsong.Marshal(value); err == nil {
    28  		json = zstring.Bytes2String(jsonByte)
    29  	} else {
    30  		json = "{}"
    31  	}
    32  
    33  	return
    34  }
    35  
    36  func parsePath(path string) (pathResult, error) {
    37  	var r pathResult
    38  	if len(path) > 0 && path[0] == ':' {
    39  		r.force = true
    40  		path = path[1:]
    41  	}
    42  	for i := 0; i < len(path); i++ {
    43  		if path[i] == '.' {
    44  			r.part = path[:i]
    45  			r.gpart = path[:i]
    46  			r.path = path[i+1:]
    47  			r.more = true
    48  			return r, nil
    49  		}
    50  		if path[i] == '*' || path[i] == '?' {
    51  			return r, ErrNotAllowedWildcard
    52  		} else if path[i] == '#' {
    53  			return r, ErrNotAllowedArrayAccess
    54  		}
    55  		if path[i] == '\\' {
    56  			epart := []byte(path[:i])
    57  			gpart := []byte(path[:i+1])
    58  			i++
    59  			if i < len(path) {
    60  				epart = append(epart, path[i])
    61  				gpart = append(gpart, path[i])
    62  				i++
    63  				for ; i < len(path); i++ {
    64  					if path[i] == '\\' {
    65  						gpart = append(gpart, '\\')
    66  						i++
    67  						if i < len(path) {
    68  							epart = append(epart, path[i])
    69  							gpart = append(gpart, path[i])
    70  						}
    71  						continue
    72  					} else if path[i] == '.' {
    73  						r.part = zstring.Bytes2String(epart)
    74  						r.gpart = zstring.Bytes2String(gpart)
    75  						r.path = path[i+1:]
    76  						r.more = true
    77  						return r, nil
    78  					} else if path[i] == '*' || path[i] == '?' {
    79  						return r, ErrNotAllowedWildcard
    80  					} else if path[i] == '#' {
    81  						return r, ErrNotAllowedArrayAccess
    82  					}
    83  					epart = append(epart, path[i])
    84  					gpart = append(gpart, path[i])
    85  				}
    86  			}
    87  			r.part = zstring.Bytes2String(epart)
    88  			r.gpart = zstring.Bytes2String(gpart)
    89  			return r, nil
    90  		}
    91  	}
    92  	r.part = path
    93  	r.gpart = path
    94  	return r, nil
    95  }
    96  
    97  func mustMarshalString(s string) bool {
    98  	for i := 0; i < len(s); i++ {
    99  		if s[i] < ' ' || s[i] > 0x7f || s[i] == '"' || s[i] == '\\' {
   100  			return true
   101  		}
   102  	}
   103  	return false
   104  }
   105  
   106  func appendStringify(buf []byte, s string) []byte {
   107  	if mustMarshalString(s) {
   108  		b, _ := jsong.Marshal(s)
   109  		return append(buf, b...)
   110  	}
   111  	buf = append(buf, '"')
   112  	buf = append(buf, s...)
   113  	buf = append(buf, '"')
   114  	return buf
   115  }
   116  
   117  func appendBuild(buf []byte, array bool, paths []pathResult, raw string,
   118  	stringify bool) []byte {
   119  	if !array {
   120  		buf = appendStringify(buf, paths[0].part)
   121  		buf = append(buf, ':')
   122  	}
   123  	if len(paths) > 1 {
   124  		n, numeric := atoui(paths[1])
   125  		if numeric || (!paths[1].force && paths[1].part == "-1") {
   126  			buf = append(buf, '[')
   127  			buf = appendRepeat(buf, "null,", n)
   128  			buf = appendBuild(buf, true, paths[1:], raw, stringify)
   129  			buf = append(buf, ']')
   130  		} else {
   131  			buf = append(buf, '{')
   132  			buf = appendBuild(buf, false, paths[1:], raw, stringify)
   133  			buf = append(buf, '}')
   134  		}
   135  	} else {
   136  		if stringify {
   137  			buf = appendStringify(buf, raw)
   138  		} else {
   139  			buf = append(buf, raw...)
   140  		}
   141  	}
   142  	return buf
   143  }
   144  
   145  func atoui(r pathResult) (n int, ok bool) {
   146  	if r.force {
   147  		return 0, false
   148  	}
   149  	for i := 0; i < len(r.part); i++ {
   150  		if r.part[i] < '0' || r.part[i] > '9' {
   151  			return 0, false
   152  		}
   153  		n = n*10 + int(r.part[i]-'0')
   154  	}
   155  	return n, true
   156  }
   157  
   158  func appendRepeat(buf []byte, s string, n int) []byte {
   159  	for i := 0; i < n; i++ {
   160  		buf = append(buf, s...)
   161  	}
   162  	return buf
   163  }
   164  
   165  func deleteTailItem(buf []byte) ([]byte, bool) {
   166  loop:
   167  	for i := len(buf) - 1; i >= 0; i-- {
   168  		switch buf[i] {
   169  		case '[':
   170  			return buf, true
   171  		case ',':
   172  			return buf[:i], false
   173  		case ':':
   174  			i--
   175  			for ; i >= 0; i-- {
   176  				if buf[i] == '"' {
   177  					i--
   178  					for ; i >= 0; i-- {
   179  						if buf[i] == '"' {
   180  							i--
   181  							if i >= 0 && buf[i] == '\\' {
   182  								i--
   183  								continue
   184  							}
   185  							for ; i >= 0; i-- {
   186  								switch buf[i] {
   187  								case '{':
   188  									return buf[:i+1], true
   189  								case ',':
   190  									return buf[:i], false
   191  								}
   192  							}
   193  						}
   194  					}
   195  					break
   196  				}
   197  			}
   198  			break loop
   199  		}
   200  	}
   201  	return buf, false
   202  }
   203  
   204  func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string,
   205  	stringify, del bool) ([]byte, error) {
   206  	var err error
   207  	var res *Res
   208  	var found bool
   209  	if del {
   210  		if paths[0].part == "-1" && !paths[0].force {
   211  			res = Get(jstr, "#")
   212  			if res.Int() > 0 {
   213  				res = Get(jstr, strconv.FormatInt(int64(res.Int()-1), 10))
   214  				found = true
   215  			}
   216  		}
   217  	}
   218  	if !found {
   219  		res = Get(jstr, paths[0].gpart)
   220  	}
   221  	if res.index > 0 {
   222  		if len(paths) > 1 {
   223  			buf = append(buf, jstr[:res.index]...)
   224  			buf, err = appendRawPaths(buf, res.raw, paths[1:], raw,
   225  				stringify, del)
   226  			if err != nil {
   227  				return nil, err
   228  			}
   229  			buf = append(buf, jstr[res.index+len(res.raw):]...)
   230  			return buf, nil
   231  		}
   232  		buf = append(buf, jstr[:res.index]...)
   233  		var exidx int
   234  		if del {
   235  			var delNextComma bool
   236  			buf, delNextComma = deleteTailItem(buf)
   237  			if delNextComma {
   238  				i, j := res.index+len(res.raw), 0
   239  				for ; i < len(jstr); i, j = i+1, j+1 {
   240  					if jstr[i] <= ' ' {
   241  						continue
   242  					}
   243  					if jstr[i] == ',' {
   244  						exidx = j + 1
   245  					}
   246  					break
   247  				}
   248  			}
   249  		} else {
   250  			if stringify {
   251  				buf = appendStringify(buf, raw)
   252  			} else {
   253  				buf = append(buf, raw...)
   254  			}
   255  		}
   256  		buf = append(buf, jstr[res.index+len(res.raw)+exidx:]...)
   257  		return buf, nil
   258  	}
   259  	if del {
   260  		return nil, ErrNoChange
   261  	}
   262  	n, numeric := atoui(paths[0])
   263  	isempty := true
   264  	for i := 0; i < len(jstr); i++ {
   265  		if jstr[i] > ' ' {
   266  			isempty = false
   267  			break
   268  		}
   269  	}
   270  	if isempty {
   271  		if numeric {
   272  			jstr = "[]"
   273  		} else {
   274  			jstr = "{}"
   275  		}
   276  	}
   277  	jsres := Parse(jstr)
   278  	if jsres.typ != JSON {
   279  		if numeric {
   280  			jstr = "[]"
   281  		} else {
   282  			jstr = "{}"
   283  		}
   284  		jsres = Parse(jstr)
   285  	}
   286  	var comma bool
   287  	for i := 1; i < len(jsres.raw); i++ {
   288  		if jsres.raw[i] <= ' ' {
   289  			continue
   290  		}
   291  		if jsres.raw[i] == '}' || jsres.raw[i] == ']' {
   292  			break
   293  		}
   294  		comma = true
   295  		break
   296  	}
   297  	switch jsres.raw[0] {
   298  	case '{':
   299  		buf = append(buf, '{')
   300  		buf = appendBuild(buf, false, paths, raw, stringify)
   301  		if comma {
   302  			buf = append(buf, ',')
   303  		}
   304  		buf = append(buf, jsres.raw[1:]...)
   305  		return buf, nil
   306  	case '[':
   307  		var appendit bool
   308  		if !numeric {
   309  			if paths[0].part == "-1" && !paths[0].force {
   310  				appendit = true
   311  			} else {
   312  				return nil, errors.New("cannot set array element for non-numeric key '" + paths[0].part + "'")
   313  			}
   314  		}
   315  		if appendit {
   316  			njson := zstring.TrimSpace(jsres.raw)
   317  			if njson[len(njson)-1] == ']' {
   318  				njson = njson[:len(njson)-1]
   319  			}
   320  			buf = append(buf, njson...)
   321  			if comma {
   322  				buf = append(buf, ',')
   323  			}
   324  
   325  			buf = appendBuild(buf, true, paths, raw, stringify)
   326  			buf = append(buf, ']')
   327  			return buf, nil
   328  		}
   329  		buf = append(buf, '[')
   330  		ress := jsres.Array()
   331  		for i := 0; i < len(ress); i++ {
   332  			if i > 0 {
   333  				buf = append(buf, ',')
   334  			}
   335  			buf = append(buf, ress[i].raw...)
   336  		}
   337  		if len(ress) == 0 {
   338  			buf = appendRepeat(buf, "null,", n-len(ress))
   339  		} else {
   340  			buf = appendRepeat(buf, ",null", n-len(ress))
   341  			if comma {
   342  				buf = append(buf, ',')
   343  			}
   344  		}
   345  		buf = appendBuild(buf, true, paths, raw, stringify)
   346  		buf = append(buf, ']')
   347  		return buf, nil
   348  	default:
   349  		return nil, ErrTypeError
   350  	}
   351  }
   352  
   353  func isOptimisticPath(path string) bool {
   354  	for i := 0; i < len(path); i++ {
   355  		if path[i] < '.' || path[i] > 'z' {
   356  			return false
   357  		}
   358  		if path[i] > '9' && path[i] < 'A' {
   359  			return false
   360  		}
   361  		if path[i] > 'z' {
   362  			return false
   363  		}
   364  	}
   365  	return true
   366  }
   367  
   368  func Marshal(json interface{}) ([]byte, error) {
   369  	return jsong.Marshal(json)
   370  }
   371  
   372  func Set(json, path string, value interface{}) (string, error) {
   373  	return SetOptions(json, path, value, nil)
   374  }
   375  
   376  func SetBytes(json []byte, path string, value interface{}) ([]byte, error) {
   377  	return SetBytesOptions(json, path, value, nil)
   378  }
   379  
   380  func SetRaw(json, path, value string) (string, error) {
   381  	return SetRawOptions(json, path, value, nil)
   382  }
   383  
   384  func SetRawOptions(json, path, value string, opts *Options) (string, error) {
   385  	var optimistic bool
   386  	if opts != nil {
   387  		optimistic = opts.Optimistic
   388  	}
   389  	res, err := set(json, path, value, false, false, optimistic, false)
   390  	if err == ErrNoChange {
   391  		return json, nil
   392  	}
   393  	return zstring.Bytes2String(res), err
   394  }
   395  
   396  func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) {
   397  	return SetRawBytesOptions(json, path, value, nil)
   398  }
   399  
   400  func Delete(json, path string) (string, error) {
   401  	return Set(json, path, dtype{})
   402  }
   403  
   404  func DeleteBytes(json []byte, path string) ([]byte, error) {
   405  	return SetBytes(json, path, dtype{})
   406  }