github.com/gogf/gf/v2@v2.7.4/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 "reflect" 12 13 "github.com/gogf/gf/v2/encoding/gini" 14 "github.com/gogf/gf/v2/encoding/gproperties" 15 "github.com/gogf/gf/v2/encoding/gtoml" 16 "github.com/gogf/gf/v2/encoding/gxml" 17 "github.com/gogf/gf/v2/encoding/gyaml" 18 "github.com/gogf/gf/v2/errors/gcode" 19 "github.com/gogf/gf/v2/errors/gerror" 20 "github.com/gogf/gf/v2/internal/json" 21 "github.com/gogf/gf/v2/internal/reflection" 22 "github.com/gogf/gf/v2/internal/rwmutex" 23 "github.com/gogf/gf/v2/os/gfile" 24 "github.com/gogf/gf/v2/text/gregex" 25 "github.com/gogf/gf/v2/text/gstr" 26 "github.com/gogf/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 }