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  }