github.com/wangyougui/gf/v2@v2.6.5/encoding/gjson/gjson_api_new_load.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package gjson
     8  
     9  import (
    10  	"bytes"
    11  	"reflect"
    12  
    13  	"github.com/wangyougui/gf/v2/encoding/gini"
    14  	"github.com/wangyougui/gf/v2/encoding/gproperties"
    15  	"github.com/wangyougui/gf/v2/encoding/gtoml"
    16  	"github.com/wangyougui/gf/v2/encoding/gxml"
    17  	"github.com/wangyougui/gf/v2/encoding/gyaml"
    18  	"github.com/wangyougui/gf/v2/errors/gcode"
    19  	"github.com/wangyougui/gf/v2/errors/gerror"
    20  	"github.com/wangyougui/gf/v2/internal/json"
    21  	"github.com/wangyougui/gf/v2/internal/reflection"
    22  	"github.com/wangyougui/gf/v2/internal/rwmutex"
    23  	"github.com/wangyougui/gf/v2/os/gfile"
    24  	"github.com/wangyougui/gf/v2/text/gregex"
    25  	"github.com/wangyougui/gf/v2/text/gstr"
    26  	"github.com/wangyougui/gf/v2/util/gconv"
    27  )
    28  
    29  // New creates a Json object with any variable type of `data`, but `data` should be a map
    30  // or slice for data access reason, or it will make no sense.
    31  //
    32  // The parameter `safe` specifies whether using this Json object in concurrent-safe context,
    33  // which is false in default.
    34  func New(data interface{}, safe ...bool) *Json {
    35  	return NewWithTag(data, string(ContentTypeJson), safe...)
    36  }
    37  
    38  // NewWithTag creates a Json object with any variable type of `data`, but `data` should be a map
    39  // or slice for data access reason, or it will make no sense.
    40  //
    41  // The parameter `tags` specifies priority tags for struct conversion to map, multiple tags joined
    42  // with char ','.
    43  //
    44  // The parameter `safe` specifies whether using this Json object in concurrent-safe context, which
    45  // is false in default.
    46  func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
    47  	option := Options{
    48  		Tags: tags,
    49  	}
    50  	if len(safe) > 0 && safe[0] {
    51  		option.Safe = true
    52  	}
    53  	return NewWithOptions(data, option)
    54  }
    55  
    56  // NewWithOptions creates a Json object with any variable type of `data`, but `data` should be a map
    57  // or slice for data access reason, or it will make no sense.
    58  func NewWithOptions(data interface{}, options Options) *Json {
    59  	var j *Json
    60  	switch data.(type) {
    61  	case string, []byte:
    62  		if r, err := loadContentWithOptions(data, options); err == nil {
    63  			j = r
    64  		} else {
    65  			j = &Json{
    66  				p:  &data,
    67  				c:  byte(defaultSplitChar),
    68  				vc: false,
    69  			}
    70  		}
    71  	default:
    72  		var (
    73  			pointedData interface{}
    74  			reflectInfo = reflection.OriginValueAndKind(data)
    75  		)
    76  		switch reflectInfo.OriginKind {
    77  		case reflect.Slice, reflect.Array:
    78  			pointedData = gconv.Interfaces(data)
    79  
    80  		case reflect.Map:
    81  			pointedData = gconv.MapDeep(data, options.Tags)
    82  
    83  		case reflect.Struct:
    84  			if v, ok := data.(iVal); ok {
    85  				return NewWithOptions(v.Val(), options)
    86  			}
    87  			pointedData = gconv.MapDeep(data, options.Tags)
    88  
    89  		default:
    90  			pointedData = data
    91  		}
    92  		j = &Json{
    93  			p:  &pointedData,
    94  			c:  byte(defaultSplitChar),
    95  			vc: false,
    96  		}
    97  	}
    98  	j.mu = rwmutex.Create(options.Safe)
    99  	return j
   100  }
   101  
   102  // Load loads content from specified file `path`, and creates a Json object from its content.
   103  func Load(path string, safe ...bool) (*Json, error) {
   104  	if p, err := gfile.Search(path); err != nil {
   105  		return nil, err
   106  	} else {
   107  		path = p
   108  	}
   109  	options := Options{
   110  		Type: ContentType(gfile.Ext(path)),
   111  	}
   112  	if len(safe) > 0 && safe[0] {
   113  		options.Safe = true
   114  	}
   115  	return doLoadContentWithOptions(gfile.GetBytesWithCache(path), options)
   116  }
   117  
   118  // LoadWithOptions creates a Json object from given JSON format content and options.
   119  func LoadWithOptions(data interface{}, options Options) (*Json, error) {
   120  	return doLoadContentWithOptions(gconv.Bytes(data), options)
   121  }
   122  
   123  // LoadJson creates a Json object from given JSON format content.
   124  func LoadJson(data interface{}, safe ...bool) (*Json, error) {
   125  	option := Options{
   126  		Type: ContentTypeJson,
   127  	}
   128  	if len(safe) > 0 && safe[0] {
   129  		option.Safe = true
   130  	}
   131  	return doLoadContentWithOptions(gconv.Bytes(data), option)
   132  }
   133  
   134  // LoadXml creates a Json object from given XML format content.
   135  func LoadXml(data interface{}, safe ...bool) (*Json, error) {
   136  	option := Options{
   137  		Type: ContentTypeXml,
   138  	}
   139  	if len(safe) > 0 && safe[0] {
   140  		option.Safe = true
   141  	}
   142  	return doLoadContentWithOptions(gconv.Bytes(data), option)
   143  }
   144  
   145  // LoadIni creates a Json object from given INI format content.
   146  func LoadIni(data interface{}, safe ...bool) (*Json, error) {
   147  	option := Options{
   148  		Type: ContentTypeIni,
   149  	}
   150  	if len(safe) > 0 && safe[0] {
   151  		option.Safe = true
   152  	}
   153  	return doLoadContentWithOptions(gconv.Bytes(data), option)
   154  }
   155  
   156  // LoadYaml creates a Json object from given YAML format content.
   157  func LoadYaml(data interface{}, safe ...bool) (*Json, error) {
   158  	option := Options{
   159  		Type: ContentTypeYaml,
   160  	}
   161  	if len(safe) > 0 && safe[0] {
   162  		option.Safe = true
   163  	}
   164  	return doLoadContentWithOptions(gconv.Bytes(data), option)
   165  }
   166  
   167  // LoadToml creates a Json object from given TOML format content.
   168  func LoadToml(data interface{}, safe ...bool) (*Json, error) {
   169  	option := Options{
   170  		Type: ContentTypeToml,
   171  	}
   172  	if len(safe) > 0 && safe[0] {
   173  		option.Safe = true
   174  	}
   175  	return doLoadContentWithOptions(gconv.Bytes(data), option)
   176  }
   177  
   178  // LoadProperties creates a Json object from given TOML format content.
   179  func LoadProperties(data interface{}, safe ...bool) (*Json, error) {
   180  	option := Options{
   181  		Type: ContentTypeProperties,
   182  	}
   183  	if len(safe) > 0 && safe[0] {
   184  		option.Safe = true
   185  	}
   186  	return doLoadContentWithOptions(gconv.Bytes(data), option)
   187  }
   188  
   189  // LoadContent creates a Json object from given content, it checks the data type of `content`
   190  // automatically, supporting data content type as follows:
   191  // JSON, XML, INI, YAML and TOML.
   192  func LoadContent(data interface{}, safe ...bool) (*Json, error) {
   193  	content := gconv.Bytes(data)
   194  	if len(content) == 0 {
   195  		return New(nil, safe...), nil
   196  	}
   197  	return LoadContentType(checkDataType(content), content, safe...)
   198  }
   199  
   200  // LoadContentType creates a Json object from given type and content,
   201  // supporting data content type as follows:
   202  // JSON, XML, INI, YAML and TOML.
   203  func LoadContentType(dataType ContentType, data interface{}, safe ...bool) (*Json, error) {
   204  	content := gconv.Bytes(data)
   205  	if len(content) == 0 {
   206  		return New(nil, safe...), nil
   207  	}
   208  	// ignore UTF8-BOM
   209  	if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
   210  		content = content[3:]
   211  	}
   212  	options := Options{
   213  		Type:      dataType,
   214  		StrNumber: true,
   215  	}
   216  	if len(safe) > 0 && safe[0] {
   217  		options.Safe = true
   218  	}
   219  	return doLoadContentWithOptions(content, options)
   220  }
   221  
   222  // IsValidDataType checks and returns whether given `dataType` a valid data type for loading.
   223  func IsValidDataType(dataType ContentType) bool {
   224  	if dataType == "" {
   225  		return false
   226  	}
   227  	if dataType[0] == '.' {
   228  		dataType = dataType[1:]
   229  	}
   230  	switch dataType {
   231  	case
   232  		ContentTypeJson,
   233  		ContentTypeJs,
   234  		ContentTypeXml,
   235  		ContentTypeYaml,
   236  		ContentTypeYml,
   237  		ContentTypeToml,
   238  		ContentTypeIni,
   239  		ContentTypeProperties:
   240  		return true
   241  	}
   242  	return false
   243  }
   244  
   245  func loadContentWithOptions(data interface{}, options Options) (*Json, error) {
   246  	content := gconv.Bytes(data)
   247  	if len(content) == 0 {
   248  		return NewWithOptions(nil, options), nil
   249  	}
   250  	if options.Type == "" {
   251  		options.Type = checkDataType(content)
   252  	}
   253  	return loadContentTypeWithOptions(content, options)
   254  }
   255  
   256  func loadContentTypeWithOptions(data interface{}, options Options) (*Json, error) {
   257  	content := gconv.Bytes(data)
   258  	if len(content) == 0 {
   259  		return NewWithOptions(nil, options), nil
   260  	}
   261  	// ignore UTF8-BOM
   262  	if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
   263  		content = content[3:]
   264  	}
   265  	return doLoadContentWithOptions(content, options)
   266  }
   267  
   268  // doLoadContent creates a Json object from given content.
   269  // It supports data content type as follows:
   270  // JSON, XML, INI, YAML and TOML.
   271  func doLoadContentWithOptions(data []byte, options Options) (*Json, error) {
   272  	var (
   273  		err    error
   274  		result interface{}
   275  	)
   276  	if len(data) == 0 {
   277  		return NewWithOptions(nil, options), nil
   278  	}
   279  	if options.Type == "" {
   280  		options.Type = checkDataType(data)
   281  	}
   282  	options.Type = ContentType(gstr.TrimLeft(
   283  		string(options.Type), "."),
   284  	)
   285  	switch options.Type {
   286  	case ContentTypeJson, ContentTypeJs:
   287  
   288  	case ContentTypeXml:
   289  		if data, err = gxml.ToJson(data); err != nil {
   290  			return nil, err
   291  		}
   292  
   293  	case ContentTypeYaml, ContentTypeYml:
   294  		if data, err = gyaml.ToJson(data); err != nil {
   295  			return nil, err
   296  		}
   297  
   298  	case ContentTypeToml:
   299  		if data, err = gtoml.ToJson(data); err != nil {
   300  			return nil, err
   301  		}
   302  
   303  	case ContentTypeIni:
   304  		if data, err = gini.ToJson(data); err != nil {
   305  			return nil, err
   306  		}
   307  	case ContentTypeProperties:
   308  		if data, err = gproperties.ToJson(data); err != nil {
   309  			return nil, err
   310  		}
   311  
   312  	default:
   313  		err = gerror.NewCodef(
   314  			gcode.CodeInvalidParameter,
   315  			`unsupported type "%s" for loading`,
   316  			options.Type,
   317  		)
   318  	}
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  	decoder := json.NewDecoder(bytes.NewReader(data))
   323  	if options.StrNumber {
   324  		decoder.UseNumber()
   325  	}
   326  	if err = decoder.Decode(&result); err != nil {
   327  		return nil, err
   328  	}
   329  	switch result.(type) {
   330  	case string, []byte:
   331  		return nil, gerror.Newf(`json decoding failed for content: %s`, data)
   332  	}
   333  	return NewWithOptions(result, options), nil
   334  }
   335  
   336  // checkDataType automatically checks and returns the data type for `content`.
   337  // Note that it uses regular expression for loose checking, you can use LoadXXX/LoadContentType
   338  // functions to load the content for certain content type.
   339  func checkDataType(content []byte) ContentType {
   340  	if json.Valid(content) {
   341  		return ContentTypeJson
   342  	} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>\s*$`, content) {
   343  		return ContentTypeXml
   344  	} else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) &&
   345  		!gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) &&
   346  		((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
   347  			(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) {
   348  		return ContentTypeYaml
   349  	} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
   350  		!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
   351  		!gregex.IsMatch(`[\n\r]+[\s\t\w\-]+\.[\s\t\w\-]+\s*=\s*.+`, content) &&
   352  		(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
   353  		return ContentTypeToml
   354  	} else if gregex.IsMatch(`\[[\w\.]+\]`, content) &&
   355  		(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
   356  		// Must contain "[xxx]" section.
   357  		return ContentTypeIni
   358  	} else if gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content) {
   359  		return ContentTypeProperties
   360  	} else {
   361  		return ""
   362  	}
   363  }