github.com/fufuok/utils@v1.0.10/xjson/sjson/sjson.go (about)

     1  // Package sjson provides setting json values.
     2  package sjson
     3  
     4  import (
     5  	jsongo "encoding/json"
     6  	"sort"
     7  	"strconv"
     8  	"unsafe"
     9  
    10  	"github.com/fufuok/utils/xjson/gjson"
    11  )
    12  
    13  type errorType struct {
    14  	msg string
    15  }
    16  
    17  func (err *errorType) Error() string {
    18  	return err.msg
    19  }
    20  
    21  // Options represents additional options for the Set and Delete functions.
    22  type Options struct {
    23  	// Optimistic is a hint that the value likely exists which
    24  	// allows for the sjson to perform a fast-track search and replace.
    25  	Optimistic bool
    26  	// ReplaceInPlace is a hint to replace the input json rather than
    27  	// allocate a new json byte slice. When this field is specified
    28  	// the input json will not longer be valid and it should not be used
    29  	// In the case when the destination slice doesn't have enough free
    30  	// bytes to replace the data in place, a new bytes slice will be
    31  	// created under the hood.
    32  	// The Optimistic flag must be set to true and the input must be a
    33  	// byte slice in order to use this field.
    34  	ReplaceInPlace bool
    35  }
    36  
    37  type pathResult struct {
    38  	part  string // current key part
    39  	gpart string // gjson get part
    40  	path  string // remaining path
    41  	force bool   // force a string key
    42  	more  bool   // there is more path to parse
    43  }
    44  
    45  func isSimpleChar(ch byte) bool {
    46  	switch ch {
    47  	case '|', '#', '@', '*', '?':
    48  		return false
    49  	default:
    50  		return true
    51  	}
    52  }
    53  
    54  func parsePath(path string) (res pathResult, simple bool) {
    55  	var r pathResult
    56  	if len(path) > 0 && path[0] == ':' {
    57  		r.force = true
    58  		path = path[1:]
    59  	}
    60  	for i := 0; i < len(path); i++ {
    61  		if path[i] == '.' {
    62  			r.part = path[:i]
    63  			r.gpart = path[:i]
    64  			r.path = path[i+1:]
    65  			r.more = true
    66  			return r, true
    67  		}
    68  		if !isSimpleChar(path[i]) {
    69  			return r, false
    70  		}
    71  		if path[i] == '\\' {
    72  			// go into escape mode. this is a slower path that
    73  			// strips off the escape character from the part.
    74  			epart := []byte(path[:i])
    75  			gpart := []byte(path[:i+1])
    76  			i++
    77  			if i < len(path) {
    78  				epart = append(epart, path[i])
    79  				gpart = append(gpart, path[i])
    80  				i++
    81  				for ; i < len(path); i++ {
    82  					if path[i] == '\\' {
    83  						gpart = append(gpart, '\\')
    84  						i++
    85  						if i < len(path) {
    86  							epart = append(epart, path[i])
    87  							gpart = append(gpart, path[i])
    88  						}
    89  						continue
    90  					} else if path[i] == '.' {
    91  						r.part = string(epart)
    92  						r.gpart = string(gpart)
    93  						r.path = path[i+1:]
    94  						r.more = true
    95  						return r, true
    96  					} else if !isSimpleChar(path[i]) {
    97  						return r, false
    98  					}
    99  					epart = append(epart, path[i])
   100  					gpart = append(gpart, path[i])
   101  				}
   102  			}
   103  			// append the last part
   104  			r.part = string(epart)
   105  			r.gpart = string(gpart)
   106  			return r, true
   107  		}
   108  	}
   109  	r.part = path
   110  	r.gpart = path
   111  	return r, true
   112  }
   113  
   114  func mustMarshalString(s string) bool {
   115  	for i := 0; i < len(s); i++ {
   116  		if s[i] < ' ' || s[i] > 0x7f || s[i] == '"' || s[i] == '\\' {
   117  			return true
   118  		}
   119  	}
   120  	return false
   121  }
   122  
   123  // appendStringify makes a json string and appends to buf.
   124  func appendStringify(buf []byte, s string) []byte {
   125  	if mustMarshalString(s) {
   126  		b, _ := jsongo.Marshal(s)
   127  		return append(buf, b...)
   128  	}
   129  	buf = append(buf, '"')
   130  	buf = append(buf, s...)
   131  	buf = append(buf, '"')
   132  	return buf
   133  }
   134  
   135  // appendBuild builds a json block from a json path.
   136  func appendBuild(buf []byte, array bool, paths []pathResult, raw string,
   137  	stringify bool,
   138  ) []byte {
   139  	if !array {
   140  		buf = appendStringify(buf, paths[0].part)
   141  		buf = append(buf, ':')
   142  	}
   143  	if len(paths) > 1 {
   144  		n, numeric := atoui(paths[1])
   145  		if numeric || (!paths[1].force && paths[1].part == "-1") {
   146  			buf = append(buf, '[')
   147  			buf = appendRepeat(buf, "null,", n)
   148  			buf = appendBuild(buf, true, paths[1:], raw, stringify)
   149  			buf = append(buf, ']')
   150  		} else {
   151  			buf = append(buf, '{')
   152  			buf = appendBuild(buf, false, paths[1:], raw, stringify)
   153  			buf = append(buf, '}')
   154  		}
   155  	} else {
   156  		if stringify {
   157  			buf = appendStringify(buf, raw)
   158  		} else {
   159  			buf = append(buf, raw...)
   160  		}
   161  	}
   162  	return buf
   163  }
   164  
   165  // atoui does a rip conversion of string -> unigned int.
   166  func atoui(r pathResult) (n int, ok bool) {
   167  	if r.force {
   168  		return 0, false
   169  	}
   170  	for i := 0; i < len(r.part); i++ {
   171  		if r.part[i] < '0' || r.part[i] > '9' {
   172  			return 0, false
   173  		}
   174  		n = n*10 + int(r.part[i]-'0')
   175  	}
   176  	return n, true
   177  }
   178  
   179  // appendRepeat repeats string "n" times and appends to buf.
   180  func appendRepeat(buf []byte, s string, n int) []byte {
   181  	for i := 0; i < n; i++ {
   182  		buf = append(buf, s...)
   183  	}
   184  	return buf
   185  }
   186  
   187  // trim does a rip trim
   188  func trim(s string) string {
   189  	for len(s) > 0 {
   190  		if s[0] <= ' ' {
   191  			s = s[1:]
   192  			continue
   193  		}
   194  		break
   195  	}
   196  	for len(s) > 0 {
   197  		if s[len(s)-1] <= ' ' {
   198  			s = s[:len(s)-1]
   199  			continue
   200  		}
   201  		break
   202  	}
   203  	return s
   204  }
   205  
   206  // deleteTailItem deletes the previous key or comma.
   207  func deleteTailItem(buf []byte) ([]byte, bool) {
   208  loop:
   209  	for i := len(buf) - 1; i >= 0; i-- {
   210  		// look for either a ',',':','['
   211  		switch buf[i] {
   212  		case '[':
   213  			return buf, true
   214  		case ',':
   215  			return buf[:i], false
   216  		case ':':
   217  			// delete tail string
   218  			i--
   219  			for ; i >= 0; i-- {
   220  				if buf[i] == '"' {
   221  					i--
   222  					for ; i >= 0; i-- {
   223  						if buf[i] == '"' {
   224  							i--
   225  							if i >= 0 && buf[i] == '\\' {
   226  								i--
   227  								continue
   228  							}
   229  							for ; i >= 0; i-- {
   230  								// look for either a ',','{'
   231  								switch buf[i] {
   232  								case '{':
   233  									return buf[:i+1], true
   234  								case ',':
   235  									return buf[:i], false
   236  								}
   237  							}
   238  						}
   239  					}
   240  					break
   241  				}
   242  			}
   243  			break loop
   244  		}
   245  	}
   246  	return buf, false
   247  }
   248  
   249  var errNoChange = &errorType{"no change"}
   250  
   251  func appendRawPaths(buf []byte, jstr string, paths []pathResult, raw string,
   252  	stringify, del bool,
   253  ) ([]byte, error) {
   254  	var err error
   255  	var res gjson.Result
   256  	var found bool
   257  	if del {
   258  		if paths[0].part == "-1" && !paths[0].force {
   259  			res = gjson.Get(jstr, "#")
   260  			if res.Int() > 0 {
   261  				res = gjson.Get(jstr, strconv.FormatInt(int64(res.Int()-1), 10))
   262  				found = true
   263  			}
   264  		}
   265  	}
   266  	if !found {
   267  		res = gjson.Get(jstr, paths[0].gpart)
   268  	}
   269  	if res.Index > 0 {
   270  		if len(paths) > 1 {
   271  			buf = append(buf, jstr[:res.Index]...)
   272  			buf, err = appendRawPaths(buf, res.Raw, paths[1:], raw,
   273  				stringify, del)
   274  			if err != nil {
   275  				return nil, err
   276  			}
   277  			buf = append(buf, jstr[res.Index+len(res.Raw):]...)
   278  			return buf, nil
   279  		}
   280  		buf = append(buf, jstr[:res.Index]...)
   281  		var exidx int // additional forward stripping
   282  		if del {
   283  			var delNextComma bool
   284  			buf, delNextComma = deleteTailItem(buf)
   285  			if delNextComma {
   286  				i, j := res.Index+len(res.Raw), 0
   287  				for ; i < len(jstr); i, j = i+1, j+1 {
   288  					if jstr[i] <= ' ' {
   289  						continue
   290  					}
   291  					if jstr[i] == ',' {
   292  						exidx = j + 1
   293  					}
   294  					break
   295  				}
   296  			}
   297  		} else {
   298  			if stringify {
   299  				buf = appendStringify(buf, raw)
   300  			} else {
   301  				buf = append(buf, raw...)
   302  			}
   303  		}
   304  		buf = append(buf, jstr[res.Index+len(res.Raw)+exidx:]...)
   305  		return buf, nil
   306  	}
   307  	if del {
   308  		return nil, errNoChange
   309  	}
   310  	n, numeric := atoui(paths[0])
   311  	isempty := true
   312  	for i := 0; i < len(jstr); i++ {
   313  		if jstr[i] > ' ' {
   314  			isempty = false
   315  			break
   316  		}
   317  	}
   318  	if isempty {
   319  		if numeric {
   320  			jstr = "[]"
   321  		} else {
   322  			jstr = "{}"
   323  		}
   324  	}
   325  	jsres := gjson.Parse(jstr)
   326  	if jsres.Type != gjson.JSON {
   327  		if numeric {
   328  			jstr = "[]"
   329  		} else {
   330  			jstr = "{}"
   331  		}
   332  		jsres = gjson.Parse(jstr)
   333  	}
   334  	var comma bool
   335  	for i := 1; i < len(jsres.Raw); i++ {
   336  		if jsres.Raw[i] <= ' ' {
   337  			continue
   338  		}
   339  		if jsres.Raw[i] == '}' || jsres.Raw[i] == ']' {
   340  			break
   341  		}
   342  		comma = true
   343  		break
   344  	}
   345  	switch jsres.Raw[0] {
   346  	default:
   347  		return nil, &errorType{"json must be an object or array"}
   348  	case '{':
   349  		end := len(jsres.Raw) - 1
   350  		for ; end > 0; end-- {
   351  			if jsres.Raw[end] == '}' {
   352  				break
   353  			}
   354  		}
   355  		buf = append(buf, jsres.Raw[:end]...)
   356  		if comma {
   357  			buf = append(buf, ',')
   358  		}
   359  		buf = appendBuild(buf, false, paths, raw, stringify)
   360  		buf = append(buf, '}')
   361  		return buf, nil
   362  	case '[':
   363  		var appendit bool
   364  		if !numeric {
   365  			if paths[0].part == "-1" && !paths[0].force {
   366  				appendit = true
   367  			} else {
   368  				return nil, &errorType{
   369  					"cannot set array element for non-numeric key '" +
   370  						paths[0].part + "'",
   371  				}
   372  			}
   373  		}
   374  		if appendit {
   375  			njson := trim(jsres.Raw)
   376  			if njson[len(njson)-1] == ']' {
   377  				njson = njson[:len(njson)-1]
   378  			}
   379  			buf = append(buf, njson...)
   380  			if comma {
   381  				buf = append(buf, ',')
   382  			}
   383  
   384  			buf = appendBuild(buf, true, paths, raw, stringify)
   385  			buf = append(buf, ']')
   386  			return buf, nil
   387  		}
   388  		buf = append(buf, '[')
   389  		ress := jsres.Array()
   390  		for i := 0; i < len(ress); i++ {
   391  			if i > 0 {
   392  				buf = append(buf, ',')
   393  			}
   394  			buf = append(buf, ress[i].Raw...)
   395  		}
   396  		if len(ress) == 0 {
   397  			buf = appendRepeat(buf, "null,", n-len(ress))
   398  		} else {
   399  			buf = appendRepeat(buf, ",null", n-len(ress))
   400  			if comma {
   401  				buf = append(buf, ',')
   402  			}
   403  		}
   404  		buf = appendBuild(buf, true, paths, raw, stringify)
   405  		buf = append(buf, ']')
   406  		return buf, nil
   407  	}
   408  }
   409  
   410  func isOptimisticPath(path string) bool {
   411  	for i := 0; i < len(path); i++ {
   412  		if path[i] < '.' || path[i] > 'z' {
   413  			return false
   414  		}
   415  		if path[i] > '9' && path[i] < 'A' {
   416  			return false
   417  		}
   418  		if path[i] > 'z' {
   419  			return false
   420  		}
   421  	}
   422  	return true
   423  }
   424  
   425  // Set sets a json value for the specified path.
   426  // A path is in dot syntax, such as "name.last" or "age".
   427  // This function expects that the json is well-formed, and does not validate.
   428  // Invalid json will not panic, but it may return back unexpected results.
   429  // An error is returned if the path is not valid.
   430  //
   431  // A path is a series of keys separated by a dot.
   432  //
   433  //	{
   434  //	  "name": {"first": "Tom", "last": "Anderson"},
   435  //	  "age":37,
   436  //	  "children": ["Sara","Alex","Jack"],
   437  //	  "friends": [
   438  //	    {"first": "James", "last": "Murphy"},
   439  //	    {"first": "Roger", "last": "Craig"}
   440  //	  ]
   441  //	}
   442  //	"name.last"          >> "Anderson"
   443  //	"age"                >> 37
   444  //	"children.1"         >> "Alex"
   445  func Set(json, path string, value interface{}) (string, error) {
   446  	return SetOptions(json, path, value, nil)
   447  }
   448  
   449  // SetBytes sets a json value for the specified path.
   450  // If working with bytes, this method preferred over
   451  // Set(string(data), path, value)
   452  func SetBytes(json []byte, path string, value interface{}) ([]byte, error) {
   453  	return SetBytesOptions(json, path, value, nil)
   454  }
   455  
   456  // SetRaw sets a raw json value for the specified path.
   457  // This function works the same as Set except that the value is set as a
   458  // raw block of json. This allows for setting premarshalled json objects.
   459  func SetRaw(json, path, value string) (string, error) {
   460  	return SetRawOptions(json, path, value, nil)
   461  }
   462  
   463  // SetRawOptions sets a raw json value for the specified path with options.
   464  // This furnction works the same as SetOptions except that the value is set
   465  // as a raw block of json. This allows for setting premarshalled json objects.
   466  func SetRawOptions(json, path, value string, opts *Options) (string, error) {
   467  	var optimistic bool
   468  	if opts != nil {
   469  		optimistic = opts.Optimistic
   470  	}
   471  	res, err := set(json, path, value, false, false, optimistic, false)
   472  	if err == errNoChange {
   473  		return json, nil
   474  	}
   475  	return string(res), err
   476  }
   477  
   478  // SetRawBytes sets a raw json value for the specified path.
   479  // If working with bytes, this method preferred over
   480  // SetRaw(string(data), path, value)
   481  func SetRawBytes(json []byte, path string, value []byte) ([]byte, error) {
   482  	return SetRawBytesOptions(json, path, value, nil)
   483  }
   484  
   485  type dtype struct{}
   486  
   487  // Delete deletes a value from json for the specified path.
   488  func Delete(json, path string) (string, error) {
   489  	return Set(json, path, dtype{})
   490  }
   491  
   492  // DeleteBytes deletes a value from json for the specified path.
   493  func DeleteBytes(json []byte, path string) ([]byte, error) {
   494  	return SetBytes(json, path, dtype{})
   495  }
   496  
   497  type stringHeader struct {
   498  	data unsafe.Pointer
   499  	len  int
   500  }
   501  
   502  type sliceHeader struct {
   503  	data unsafe.Pointer
   504  	len  int
   505  	cap  int
   506  }
   507  
   508  func set(jstr, path, raw string,
   509  	stringify, del, optimistic, inplace bool,
   510  ) ([]byte, error) {
   511  	if path == "" {
   512  		return []byte(jstr), &errorType{"path cannot be empty"}
   513  	}
   514  	if !del && optimistic && isOptimisticPath(path) {
   515  		res := gjson.Get(jstr, path)
   516  		if res.Exists() && res.Index > 0 {
   517  			sz := len(jstr) - len(res.Raw) + len(raw)
   518  			if stringify {
   519  				sz += 2
   520  			}
   521  			if inplace && sz <= len(jstr) {
   522  				if !stringify || !mustMarshalString(raw) {
   523  					jsonh := *(*stringHeader)(unsafe.Pointer(&jstr))
   524  					jsonbh := sliceHeader{
   525  						data: jsonh.data, len: jsonh.len, cap: jsonh.len,
   526  					}
   527  					jbytes := *(*[]byte)(unsafe.Pointer(&jsonbh))
   528  					if stringify {
   529  						jbytes[res.Index] = '"'
   530  						copy(jbytes[res.Index+1:], []byte(raw))
   531  						jbytes[res.Index+1+len(raw)] = '"'
   532  						copy(jbytes[res.Index+1+len(raw)+1:],
   533  							jbytes[res.Index+len(res.Raw):])
   534  					} else {
   535  						copy(jbytes[res.Index:], []byte(raw))
   536  						copy(jbytes[res.Index+len(raw):],
   537  							jbytes[res.Index+len(res.Raw):])
   538  					}
   539  					return jbytes[:sz], nil
   540  				}
   541  				return []byte(jstr), nil
   542  			}
   543  			buf := make([]byte, 0, sz)
   544  			buf = append(buf, jstr[:res.Index]...)
   545  			if stringify {
   546  				buf = appendStringify(buf, raw)
   547  			} else {
   548  				buf = append(buf, raw...)
   549  			}
   550  			buf = append(buf, jstr[res.Index+len(res.Raw):]...)
   551  			return buf, nil
   552  		}
   553  	}
   554  	var paths []pathResult
   555  	r, simple := parsePath(path)
   556  	if simple {
   557  		paths = append(paths, r)
   558  		for r.more {
   559  			r, simple = parsePath(r.path)
   560  			if !simple {
   561  				break
   562  			}
   563  			paths = append(paths, r)
   564  		}
   565  	}
   566  	if !simple {
   567  		if del {
   568  			return []byte(jstr),
   569  				&errorType{"cannot delete value from a complex path"}
   570  		}
   571  		return setComplexPath(jstr, path, raw, stringify)
   572  	}
   573  	njson, err := appendRawPaths(nil, jstr, paths, raw, stringify, del)
   574  	if err != nil {
   575  		return []byte(jstr), err
   576  	}
   577  	return njson, nil
   578  }
   579  
   580  func setComplexPath(jstr, path, raw string, stringify bool) ([]byte, error) {
   581  	res := gjson.Get(jstr, path)
   582  	if !res.Exists() || !(res.Index != 0 || len(res.Indexes) != 0) {
   583  		return []byte(jstr), errNoChange
   584  	}
   585  	if res.Index != 0 {
   586  		njson := []byte(jstr[:res.Index])
   587  		if stringify {
   588  			njson = appendStringify(njson, raw)
   589  		} else {
   590  			njson = append(njson, raw...)
   591  		}
   592  		njson = append(njson, jstr[res.Index+len(res.Raw):]...)
   593  		jstr = string(njson)
   594  	}
   595  	if len(res.Indexes) > 0 {
   596  		type val struct {
   597  			index int
   598  			res   gjson.Result
   599  		}
   600  		vals := make([]val, 0, len(res.Indexes))
   601  		res.ForEach(func(_, vres gjson.Result) bool {
   602  			vals = append(vals, val{res: vres})
   603  			return true
   604  		})
   605  		if len(res.Indexes) != len(vals) {
   606  			return []byte(jstr), errNoChange
   607  		}
   608  		for i := 0; i < len(res.Indexes); i++ {
   609  			vals[i].index = res.Indexes[i]
   610  		}
   611  		sort.SliceStable(vals, func(i, j int) bool {
   612  			return vals[i].index > vals[j].index
   613  		})
   614  		for _, val := range vals {
   615  			vres := val.res
   616  			index := val.index
   617  			njson := []byte(jstr[:index])
   618  			if stringify {
   619  				njson = appendStringify(njson, raw)
   620  			} else {
   621  				njson = append(njson, raw...)
   622  			}
   623  			njson = append(njson, jstr[index+len(vres.Raw):]...)
   624  			jstr = string(njson)
   625  		}
   626  	}
   627  	return []byte(jstr), nil
   628  }
   629  
   630  // SetOptions sets a json value for the specified path with options.
   631  // A path is in dot syntax, such as "name.last" or "age".
   632  // This function expects that the json is well-formed, and does not validate.
   633  // Invalid json will not panic, but it may return back unexpected results.
   634  // An error is returned if the path is not valid.
   635  func SetOptions(json, path string, value interface{},
   636  	opts *Options,
   637  ) (string, error) {
   638  	if opts != nil {
   639  		if opts.ReplaceInPlace {
   640  			// it's not safe to replace bytes in-place for strings
   641  			// copy the Options and set options.ReplaceInPlace to false.
   642  			nopts := *opts
   643  			opts = &nopts
   644  			opts.ReplaceInPlace = false
   645  		}
   646  	}
   647  	jsonh := *(*stringHeader)(unsafe.Pointer(&json))
   648  	jsonbh := sliceHeader{data: jsonh.data, len: jsonh.len, cap: jsonh.len}
   649  	jsonb := *(*[]byte)(unsafe.Pointer(&jsonbh))
   650  	res, err := SetBytesOptions(jsonb, path, value, opts)
   651  	return string(res), err
   652  }
   653  
   654  // SetBytesOptions sets a json value for the specified path with options.
   655  // If working with bytes, this method preferred over
   656  // SetOptions(string(data), path, value)
   657  func SetBytesOptions(json []byte, path string, value interface{},
   658  	opts *Options,
   659  ) ([]byte, error) {
   660  	var optimistic, inplace bool
   661  	if opts != nil {
   662  		optimistic = opts.Optimistic
   663  		inplace = opts.ReplaceInPlace
   664  	}
   665  	jstr := *(*string)(unsafe.Pointer(&json))
   666  	var res []byte
   667  	var err error
   668  	switch v := value.(type) {
   669  	default:
   670  		b, merr := jsongo.Marshal(value)
   671  		if merr != nil {
   672  			return nil, merr
   673  		}
   674  		raw := *(*string)(unsafe.Pointer(&b))
   675  		res, err = set(jstr, path, raw, false, false, optimistic, inplace)
   676  	case dtype:
   677  		res, err = set(jstr, path, "", false, true, optimistic, inplace)
   678  	case string:
   679  		res, err = set(jstr, path, v, true, false, optimistic, inplace)
   680  	case []byte:
   681  		raw := *(*string)(unsafe.Pointer(&v))
   682  		res, err = set(jstr, path, raw, true, false, optimistic, inplace)
   683  	case bool:
   684  		if v {
   685  			res, err = set(jstr, path, "true", false, false, optimistic, inplace)
   686  		} else {
   687  			res, err = set(jstr, path, "false", false, false, optimistic, inplace)
   688  		}
   689  	case int8:
   690  		res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
   691  			false, false, optimistic, inplace)
   692  	case int16:
   693  		res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
   694  			false, false, optimistic, inplace)
   695  	case int32:
   696  		res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
   697  			false, false, optimistic, inplace)
   698  	case int64:
   699  		res, err = set(jstr, path, strconv.FormatInt(int64(v), 10),
   700  			false, false, optimistic, inplace)
   701  	case uint8:
   702  		res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
   703  			false, false, optimistic, inplace)
   704  	case uint16:
   705  		res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
   706  			false, false, optimistic, inplace)
   707  	case uint32:
   708  		res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
   709  			false, false, optimistic, inplace)
   710  	case uint64:
   711  		res, err = set(jstr, path, strconv.FormatUint(uint64(v), 10),
   712  			false, false, optimistic, inplace)
   713  	case float32:
   714  		res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
   715  			false, false, optimistic, inplace)
   716  	case float64:
   717  		res, err = set(jstr, path, strconv.FormatFloat(float64(v), 'f', -1, 64),
   718  			false, false, optimistic, inplace)
   719  	}
   720  	if err == errNoChange {
   721  		return json, nil
   722  	}
   723  	return res, err
   724  }
   725  
   726  // SetRawBytesOptions sets a raw json value for the specified path with options.
   727  // If working with bytes, this method preferred over
   728  // SetRawOptions(string(data), path, value, opts)
   729  func SetRawBytesOptions(json []byte, path string, value []byte,
   730  	opts *Options,
   731  ) ([]byte, error) {
   732  	jstr := *(*string)(unsafe.Pointer(&json))
   733  	vstr := *(*string)(unsafe.Pointer(&value))
   734  	var optimistic, inplace bool
   735  	if opts != nil {
   736  		optimistic = opts.Optimistic
   737  		inplace = opts.ReplaceInPlace
   738  	}
   739  	res, err := set(jstr, path, vstr, false, false, optimistic, inplace)
   740  	if err == errNoChange {
   741  		return json, nil
   742  	}
   743  	return res, err
   744  }