github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/json.go (about) 1 package easy 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "reflect" 8 "strconv" 9 "strings" 10 "unicode/utf8" 11 "unsafe" 12 13 "github.com/mitchellh/mapstructure" 14 "github.com/tidwall/gjson" 15 16 "github.com/jxskiss/gopkg/v2/easy/ezmap" 17 "github.com/jxskiss/gopkg/v2/internal/unsafeheader" 18 "github.com/jxskiss/gopkg/v2/perf/json" 19 ) 20 21 // JSON converts given object to a json string, it never returns error. 22 // The marshalling method used here does not escape HTML characters, 23 // and map keys are sorted, which helps human reading. 24 func JSON(v any) string { 25 b, err := json.HumanFriendly.Marshal(v) 26 if err != nil { 27 return fmt.Sprintf("<error: %v>", err) 28 } 29 b = bytes.TrimSpace(b) 30 return unsafeheader.BytesToString(b) 31 } 32 33 // LazyJSON returns a lazy object which wraps v, and it marshals v 34 // using JSON when it's String method is called. 35 // This helps to avoid unnecessary marshaling in some use case, 36 // such as leveled logging. 37 func LazyJSON(v any) fmt.Stringer { 38 return lazyString{f: JSON, v: v} 39 } 40 41 // LazyFunc returns a lazy object which wraps v, 42 // which marshals v using f when it's String method is called. 43 // This helps to avoid unnecessary marshaling in some use case, 44 // such as leveled logging. 45 func LazyFunc(v any, f func(any) string) fmt.Stringer { 46 return lazyString{f: f, v: v} 47 } 48 49 type lazyString struct { 50 f func(any) string 51 v any 52 } 53 54 func (x lazyString) String() string { return x.f(x.v) } 55 56 // Pretty converts given object to a pretty formatted json string. 57 // If the input is a json string, it will be formatted using json.Indent 58 // with four space characters as indent. 59 func Pretty(v any) string { 60 return prettyIndent(v, " ") 61 } 62 63 // Pretty2 is like Pretty, but it uses two space characters as indent, 64 // instead of four. 65 func Pretty2(v any) string { 66 return prettyIndent(v, " ") 67 } 68 69 func prettyIndent(v any, indent string) string { 70 var src []byte 71 switch v := v.(type) { 72 case []byte: 73 src = v 74 case string: 75 src = unsafeheader.StringToBytes(v) 76 } 77 if src != nil { 78 if json.Valid(src) { 79 buf := bytes.NewBuffer(nil) 80 _ = json.Indent(buf, src, "", indent) 81 return unsafeheader.BytesToString(buf.Bytes()) 82 } 83 if utf8.Valid(src) { 84 return string(src) 85 } 86 return fmt.Sprintf("<pretty: non-printable bytes of length %d>", len(src)) 87 } 88 buf, err := json.HumanFriendly.MarshalIndent(v, "", indent) 89 if err != nil { 90 return fmt.Sprintf("<error: %v>", err) 91 } 92 buf = bytes.TrimSpace(buf) 93 return unsafeheader.BytesToString(buf) 94 } 95 96 type JSONPathMapping [][3]string 97 98 // ParseJSONRecordsWithMapping parses gjson.Result array to slice of 99 // map[string]any according to json path mapping. 100 func ParseJSONRecordsWithMapping(arr []gjson.Result, mapping JSONPathMapping) []ezmap.Map { 101 out := make([]ezmap.Map, 0, len(arr)) 102 mapper := &jsonMapper{} 103 convFuncs := mapper.getConvFuncs(mapping) 104 for _, row := range arr { 105 result := mapper.parseRecord(row, mapping, convFuncs) 106 out = append(out, result) 107 } 108 return out 109 } 110 111 // ParseJSONRecords parses gjson.Result array to slice of *T 112 // according to json path mapping defined by struct tag "mapping". 113 // 114 // Note: 115 // 116 // 1. The type parameter T must be a struct 117 // 2. It has very limited support for complex types of struct fields, 118 // e.g. []any, []*Struct, []map[string]any, 119 // map[string]any, map[string]*Struct, map[string]map[string]any 120 func ParseJSONRecords[T any](dst *[]*T, records []gjson.Result, opts ...JSONMapperOpt) error { 121 var sample T 122 if reflect.TypeOf(sample).Kind() != reflect.Struct { 123 return errors.New("ParseJSONRecords: type T must be a struct") 124 } 125 mapper := &jsonMapper{ 126 opts: *(new(jsonMapperOptions).Apply(opts...)), 127 } 128 mapping, err := mapper.parseStructMapping(sample, nil) 129 if err != nil { 130 return err 131 } 132 convFuncs := mapper.getConvFuncs(mapping) 133 out := make([]map[string]any, 0, len(records)) 134 for _, row := range records { 135 result := mapper.parseRecord(row, mapping, convFuncs) 136 out = append(out, result) 137 } 138 return mapstructure.Decode(out, &dst) 139 } 140 141 type jsonConvFunc func(j gjson.Result, path string) any 142 143 type jsonMapper struct { 144 opts jsonMapperOptions 145 convFuncs map[[3]string]jsonConvFunc 146 structMapping map[string]JSONPathMapping 147 } 148 149 func (p *jsonMapper) parseRecord(j gjson.Result, mapping JSONPathMapping, convFuncs []jsonConvFunc) map[string]any { 150 if !j.Exists() { 151 return nil 152 } 153 result := make(map[string]any) 154 for i, x := range mapping { 155 key, path := x[0], x[1] 156 value := convFuncs[i](j, path) 157 result[key] = value 158 } 159 return result 160 } 161 162 func (p *jsonMapper) getConvFuncs(mapping JSONPathMapping) []jsonConvFunc { 163 funcs := make([]jsonConvFunc, len(mapping)) 164 for i, x := range mapping { 165 f, exists := p.convFuncs[x] 166 if !exists { 167 path, typ := x[1], x[2] 168 f = p.newConvFunc(path, typ) 169 if p.convFuncs == nil { 170 p.convFuncs = make(map[[3]string]jsonConvFunc) 171 } 172 p.convFuncs[x] = f 173 } 174 funcs[i] = f 175 } 176 return funcs 177 } 178 179 func (p *jsonMapper) newConvFunc(path, typ string) jsonConvFunc { 180 path = strings.TrimSpace(path) 181 switch typ { 182 case "", "str", "string": // default "str" 183 return func(j gjson.Result, path string) any { 184 return j.Get(path).String() 185 } 186 case "bool": 187 return func(j gjson.Result, path string) any { 188 return j.Get(path).Bool() 189 } 190 case "int", "int8", "int16", "int32", "int64": 191 return func(j gjson.Result, path string) any { 192 return j.Get(path).Int() 193 } 194 case "uint", "uint8", "uint16", "uint32", "uint64": 195 return func(j gjson.Result, path string) any { 196 return j.Get(path).Uint() 197 } 198 case "float", "float32", "float64": 199 return func(j gjson.Result, path string) any { 200 return j.Get(path).Float() 201 } 202 case "time": 203 return func(j gjson.Result, path string) any { 204 return j.Get(path).Time() 205 } 206 case "struct": 207 var ( 208 subMapping JSONPathMapping 209 subConvFuncs []jsonConvFunc 210 ) 211 path, subMapping = p.parseSubMapping(path) 212 if len(subMapping) > 0 { 213 return func(j gjson.Result, _ string) any { 214 if !j.Exists() { 215 return nil 216 } 217 if subConvFuncs == nil { 218 subConvFuncs = p.getConvFuncs(subMapping) 219 } 220 return p.parseRecord(j.Get(path), subMapping, subConvFuncs) 221 } 222 } 223 case "map": 224 var ( 225 subMapping JSONPathMapping 226 subConvFuncs []jsonConvFunc 227 ) 228 path, subMapping = p.parseSubMapping(path) 229 if len(subMapping) > 0 { 230 return func(j gjson.Result, _ string) any { 231 j = j.Get(path) 232 if !j.Exists() { 233 return nil 234 } 235 if subConvFuncs == nil { 236 subConvFuncs = p.getConvFuncs(subMapping) 237 } 238 out := make(map[string]any) 239 for k, v := range j.Map() { 240 out[k] = p.parseRecord(v, subMapping, subConvFuncs) 241 } 242 return out 243 } 244 } 245 case "arr", "array": 246 var ( 247 subMapping JSONPathMapping 248 subConvFuncs []jsonConvFunc 249 ) 250 path, subMapping = p.parseSubMapping(path) 251 if len(subMapping) > 0 { 252 return func(j gjson.Result, _ string) any { 253 j = j.Get(path) 254 if !j.Exists() { 255 return nil 256 } 257 if subConvFuncs == nil { 258 subConvFuncs = p.getConvFuncs(subMapping) 259 } 260 out := make([]any, 0) 261 for _, x := range j.Array() { 262 out = append(out, p.parseRecord(x, subMapping, subConvFuncs)) 263 } 264 return out 265 } 266 } 267 } 268 // fallback any 269 return func(j gjson.Result, path string) any { 270 j = j.Get(path) 271 if !j.Exists() { 272 return nil 273 } 274 return j.Value() 275 } 276 } 277 278 func (p *jsonMapper) parseSubMapping(str string) (path string, subMapping JSONPathMapping) { 279 str = strings.TrimSpace(str) 280 nlIdx := strings.IndexByte(str, '\n') 281 if nlIdx <= 0 { 282 return "", nil 283 } 284 var mapping JSONPathMapping 285 path = str[:nlIdx] 286 subPath := str[nlIdx+1:] 287 if p.isStructMappingKey(subPath) { 288 mapping = p.structMapping[subPath] 289 } else { 290 err := json.Unmarshal([]byte(subPath), &mapping) 291 if err != nil { 292 panic(err) 293 } 294 } 295 return path, mapping 296 } 297 298 func (p *jsonMapper) isStructMappingKey(s string) bool { 299 return strings.HasPrefix(s, "\t\tSTRUCT\t") 300 } 301 302 func (p *jsonMapper) getStructMappingKey(typ reflect.Type) string { 303 // type iface { tab *itab, data unsafe.Pointer } 304 typeptr := (*(*[2]uintptr)(unsafe.Pointer(&typ)))[1] 305 return "\t\tSTRUCT\t" + strconv.FormatUint(uint64(typeptr), 32) 306 } 307 308 func (p *jsonMapper) parseStructMapping(sample any, seenTypes []reflect.Type) (mapping JSONPathMapping, err error) { 309 structTyp := reflect.TypeOf(sample) 310 seenTypes = append(seenTypes, structTyp) 311 defer func() { 312 if err == nil { 313 key := p.getStructMappingKey(structTyp) 314 if p.structMapping == nil { 315 p.structMapping = make(map[string]JSONPathMapping) 316 } 317 p.structMapping[key] = mapping 318 } 319 }() 320 321 numField := structTyp.NumField() 322 for i := 0; i < numField; i++ { 323 field := structTyp.Field(i) 324 jsonPath := field.Tag.Get("mapping") 325 if jsonPath == "-" || !field.IsExported() { 326 continue 327 } 328 if jsonPath == "" { 329 jsonPath = field.Name 330 } 331 if dynPath := p.opts.DynamicMapping[jsonPath]; dynPath != "" { 332 jsonPath = dynPath 333 } 334 _fieldTyp := field.Type 335 if _fieldTyp.Kind() == reflect.Pointer { 336 _fieldTyp = _fieldTyp.Elem() 337 } 338 kind := p.getKind(_fieldTyp) 339 var mappingType string 340 switch kind { 341 case reflect.String: 342 mappingType = "str" 343 case reflect.Bool: 344 mappingType = "bool" 345 case reflect.Int: 346 mappingType = "int" 347 case reflect.Uint: 348 mappingType = "uint" 349 case reflect.Float32: 350 mappingType = "float" 351 case reflect.Struct: 352 if _fieldTyp.String() == "time.Time" { 353 mappingType = "time" 354 } else { 355 mappingType = "struct" 356 subStructKey := p.getStructMappingKey(_fieldTyp) 357 if _, ok := p.structMapping[subStructKey]; !ok && 358 !isSeenType(seenTypes, _fieldTyp) { 359 _, err := p.parseStructMapping(reflect.New(_fieldTyp).Elem().Interface(), seenTypes) 360 if err != nil { 361 return nil, err 362 } 363 } 364 jsonPath += "\n" + subStructKey 365 } 366 case reflect.Array, reflect.Slice: 367 elemTyp := field.Type.Elem() 368 if elemTyp.Kind() == reflect.Pointer { 369 elemTyp = elemTyp.Elem() 370 } 371 isSupported, isStruct := p.isSupportedElemType(elemTyp) 372 if !isSupported { 373 return nil, fmt.Errorf("unsupported array/slice element type: %v", field.Type) 374 } 375 mappingType = "array" 376 if isStruct { 377 if elemTyp.Kind() == reflect.Pointer { 378 elemTyp = elemTyp.Elem() 379 } 380 subStructKey := p.getStructMappingKey(elemTyp) 381 if _, ok := p.structMapping[subStructKey]; !ok && 382 !isSeenType(seenTypes, elemTyp) { 383 _, err := p.parseStructMapping(reflect.New(elemTyp).Elem().Interface(), seenTypes) 384 if err != nil { 385 return nil, err 386 } 387 } 388 jsonPath += "\n" + subStructKey 389 } 390 case reflect.Map: 391 keyType := field.Type.Key() 392 if keyType.Kind() != reflect.String { 393 return nil, fmt.Errorf("unsupported map key type: %v", field.Type) 394 } 395 elemTyp := field.Type.Elem() 396 if elemTyp.Kind() == reflect.Pointer { 397 elemTyp = elemTyp.Elem() 398 } 399 isSupported, isStruct := p.isSupportedElemType(elemTyp) 400 if !isSupported { 401 return nil, fmt.Errorf("unsupported map element type: %v", field.Type) 402 } 403 mappingType = "map" 404 if isStruct { 405 if elemTyp.Kind() == reflect.Pointer { 406 elemTyp = elemTyp.Elem() 407 } 408 subStructKey := p.getStructMappingKey(elemTyp) 409 if _, ok := p.structMapping[subStructKey]; !ok && 410 !isSeenType(seenTypes, elemTyp) { 411 _, err := p.parseStructMapping(reflect.New(elemTyp).Elem().Interface(), seenTypes) 412 if err != nil { 413 return nil, err 414 } 415 } 416 jsonPath += "\n" + subStructKey 417 } 418 default: 419 return nil, fmt.Errorf("unsupported field type: %v", field.Type) 420 } 421 mapping = append(mapping, [3]string{field.Name, jsonPath, mappingType}) 422 } 423 return mapping, nil 424 } 425 426 func isSeenType(stack []reflect.Type, typ reflect.Type) bool { 427 for i := range stack { 428 if stack[i] == typ { 429 return true 430 } 431 } 432 return false 433 } 434 435 var ( 436 anyTyp = reflect.TypeOf((*any)(nil)).Elem() 437 anyMapTyp = reflect.TypeOf((*map[string]any)(nil)).Elem() 438 ) 439 440 func (p *jsonMapper) isSupportedElemType(typ reflect.Type) (isSupported, isStruct bool) { 441 if typ.Kind() == reflect.Pointer { 442 typ = typ.Elem() 443 } 444 kind := p.getKind(typ) 445 if typ == anyTyp || typ == anyMapTyp || 446 kind == reflect.Bool || 447 kind == reflect.Int || 448 kind == reflect.Uint || 449 kind == reflect.Float32 || 450 kind == reflect.String || 451 kind == reflect.Struct { 452 isSupported = true 453 } 454 if kind == reflect.Struct { 455 isStruct = true 456 } 457 return 458 } 459 460 func (p *jsonMapper) getKind(typ reflect.Type) reflect.Kind { 461 kind := typ.Kind() 462 switch { 463 case kind >= reflect.Int && kind <= reflect.Int64: 464 return reflect.Int 465 case kind >= reflect.Uint && kind <= reflect.Uint64: 466 return reflect.Uint 467 case kind == reflect.Float32 || kind == reflect.Float64: 468 return reflect.Float32 469 default: 470 return kind 471 } 472 } 473 474 // JSONMapperOpt customizes the behavior of parsing JSON records. 475 type JSONMapperOpt struct { 476 apply func(options *jsonMapperOptions) 477 } 478 479 type jsonMapperOptions struct { 480 DynamicMapping map[string]string 481 } 482 483 func (p *jsonMapperOptions) Apply(opts ...JSONMapperOpt) *jsonMapperOptions { 484 for _, opt := range opts { 485 opt.apply(p) 486 } 487 return p 488 } 489 490 // WithDynamicJSONMapping specifies dynamic JSON path mapping to use, 491 // if a key specified by struct tag "mapping" is found in mapping, 492 // the JSON path expression is replaced by the value from mapping. 493 func WithDynamicJSONMapping(mapping map[string]string) JSONMapperOpt { 494 return JSONMapperOpt{ 495 apply: func(options *jsonMapperOptions) { 496 options.DynamicMapping = mapping 497 }, 498 } 499 }