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 }