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 }