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