JSON现在宽泛用于配置和通信协议,但因为其定义的灵活性,很容易传递谬误数据。本文介绍了如何应用mapstructure工具实现动静灵便的JSON数据解析,在就义肯定性能的前提下,无效晋升开发效率和容错能力。原文: Efficient JSON Data Handling: Dynamic Parsing Tips in Golang

打造无缝 Golang 体验,摸索动静 JSON 解析技术,实现最佳开发实际。

在 Golang 开发畛域,常常须要解析 JSON 数据。然而,如果值的类型不确定,是否有优雅的解决方案?

例如,当 JSON 字符串为 { "age":1 },而相应的构造体定义为字符串时,解析就会报错。

除了为构造体定义反序列化办法外,还有其余解决方案吗?明天,我将介绍另一种解决这一难题的办法。

Mapstructure 次要用于将任意 JSON 数据解码为 Go 构造。在解决 JSON 数据中的动静或不确定类型时,这将是一个弱小的工具,提供了灵便的解决方案,超过了僵化构造定义的限度。

实质上讲,它善于解析数据流,并将其映射到定义的构造中。

咱们通过几个例子来探讨如何应用 mapstructure

1.惯例用处
type Person struct {    Name   string    Age    int    Emails []string    Extra  map[string]string}func normalDecode() {    input := map[string]interface{}{      "name":   "Foo",      "age":    21,      "emails": []string{"one@gmail.com", "two@gmail.com", "three@gmail.com"},      "extra": map[string]string{         "twitter": "Foo",      },   }      var result Person   err := mapstructure.Decode(input, &result)   if err != nil {      panic(err)   }   fmt.Printf("%#v\n", result)}

后果:

main.Person{Name:"Foo", Age:21, Emails:[]string{"one@gmail.com", "two@gmail.com", "three@gmail.com"}, Extra:map[string]string{"twitter":"Foo"}}

这种办法可能是最罕用的,能够毫不费力地将 map[string]interface{} 映射到咱们定义的构造。

在这里,咱们并没有为每个字段指定标签,而是让 mapstructure 主动解决映射。

如果输出是 JSON 字符串,咱们首先将其解析为 map[string]interface{} 格局,而后将其映射到构造中。

func jsonDecode() {     var jsonStr = `{         "name": "Foo",         "age": 21,         "gender": "male"     }`         type Person struct {          Name   string          Age    int          Gender string     }     m := make(map[string]interface{})     err := json.Unmarshal([]byte(jsonStr), &m)     if err != nil {          panic(err)     }          var result Person     err = mapstructure.Decode(m, &result)     if err != nil {          panic(err.Error())     }     fmt.Printf("%#v\n", result)}

后果:

main.Person{Name:"Foo", Age:21, Gender:"male"}
2.嵌入式构造

mapstructure 使咱们可能压缩多个嵌入式构造,并应用 squash 标记来解决。

type School struct {    Name string}type Address struct {    City string}type Person struct {    School    `mapstructure:",squash"`    Address  `mapstructure:",squash"`    Email      string}func embeddedStructDecode() {   input := map[string]interface{}{      "Name": "A1",      "City":  "B1",      "Email": "C1",   }      var result Person   err := mapstructure.Decode(input, &result)   if err != nil {      panic(err)   }      fmt.Printf("%s %s, %s\n", result.Name, result.City, result.Email)}

后果:

A1, B1, C1

在这个例子中,Person 蕴含了 SchoolAddress 的嵌入式构造,并通过应用 squash 标签实现了扁平化成果。

3.元数据
type Person struct {    Name   string    Age    int    Gender string}func metadataDecode() {   input := map[string]interface{}{      "name":  "A1",      "age":   1,      "email": "B1",   }      var md mapstructure.Metadata   var result Person   config := &mapstructure.DecoderConfig{      Metadata: &md,      Result:   &result,   }      decoder, err := mapstructure.NewDecoder(config)   if err != nil {      panic(err)   }      if err = decoder.Decode(input); err != nil {      panic(err)   }      fmt.Printf("value: %#v, keys: %#v, Unused keys: %#v, Unset keys: %#v\n", result, md.Keys, md.Unused, md.Unset)}

后果:

value: main.Person{Name:"A1", Age:1, Gender:""}, keys: []string{"Name", "Age"}, Unused keys: []string{"email"}, Unset keys: []string{"Gender"}

从这个例子中,咱们能够看到,应用元数据能够跟踪咱们的构造与 map[string]interface{} 之间的差别。雷同局部能够正确映射到相应的字段,而不同的局部则应用 UnusedUnset 来示意。

  • Unused: map中存在但构造中没有的字段。
  • Unset: 构造中存在但map中没有的字段。
4.防止空值映射

这里的用法相似于内置 json 软件包,利用 omitempty 标记来解决空值的映射。

type School struct {  Name string}type Address struct {  City string}type Person struct {  *School   `mapstructure:",omitempty"`  *Address `mapstructure:",omitempty"`  Age       int  Email     string}func omitemptyDecode() {   result := &map[string]interface{}{}   input := Person{Email: "C1"}   err := mapstructure.Decode(input, &result)   if err != nil {      panic(err)   }     fmt.Printf("%+v\n", result)}

后果:

&map[Age:0 Email:C1]

留神这里的 *School*Address 都被标记为 omitempty,即在解析过程中疏忽空值。

另一方面,Age 没有应用 omitempty 标记,因为输出中没有相应的值,解析时应用了相应类型的零值,int 的零值为 0

type Person struct {    Name  string    Age   int    Other map[string]interface{} `mapstructure:",remain"`}func remainDataDecode() {   input := map[string]interface{}{      "name":   "A1",      "age":    1,      "email":  "B1",      "gender": "C1",   }     var result Person   err := mapstructure.Decode(input, &result)   if err != nil {      panic(err)   }     fmt.Printf("%#v\n", result)}

后果:

main.Person{Name:"A1", Age:1, Other:map[string]interface {}{"email":"B1", "gender":"C1"}}

从代码中能够看出,Other 字段被标记为 remain,意味着输出中任何不能正确映射的字段都将被放在 Other 字段中。

输入结果显示,emailgender 已被正确的放入 Other

5.自定义标签
type Person struct {    Name string `mapstructure:"person_name"`    Age  int    `mapstructure:"person_age"`}func tagDecode() {   input := map[string]interface{}{      "person_name": "A1",      "person_age":  1,   }      var result Person   err := mapstructure.Decode(input, &result)   if err != nil {      panic(err)   }     fmt.Printf("%#v\n", result)}

后果:

main.Person{Name:"A1", Age:1}

Person 构造中,咱们将 person_nameperson_age 别离映射为 NameAge,从而在不扭转构造的状况下实现了正确的解析。

6.弱类型解析
type Person struct {    Name   string    Age    int    Emails []string}func weaklyTypedInputDecode() {   input := map[string]interface{}{    "name":   123,  // number => string    "age":    "11", // string => number    "emails": map[string]interface{}{}, // empty map => empty array   }     var result Person   config := &mapstructure.DecoderConfig{      WeaklyTypedInput: true,      Result:           &result,   }     decoder, err := mapstructure.NewDecoder(config)   if err != nil {      panic(err)   }     err = decoder.Decode(input)   if err != nil {      panic(err)   }     fmt.Printf("%#v\n", result)}

后果:

main.Person{Name:"123", Age:11, Emails:[]string{}}

从代码中能够看出,输出的 nameage 类型与 Person 构造中的 NameAge 类型不匹配。

email 字段尤其不走寻常路,一个是字符串数组,另一个是map。

通过自定义 DecoderConfig 并将 WeaklyTypedInput 设置为 truemapstructure 能够轻松解决此类弱类型解析问题。

不过,须要留神的是,并非所有问题都能失去解决,源代码也存在肯定的局限性:

//   - bools to string (true = "1", false = "0")//   - numbers to string (base 10)//   - bools to int/uint (true = 1, false = 0)//   - strings to int/uint (base implied by prefix)//   - int to bool (true if value != 0)//   - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,//     FALSE, false, False. Anything else is an error)//   - empty array = empty map and vice versa//   - negative numbers to overflowed uint values (base 10)//   - slice of maps to a merged map//   - single values are converted to slices if required. Each//     element is weakly decoded. For example: "4" can become []int{4}//     if the target type is an int slice.
7.错误处理

Mapstructure 提供了用户十分方便使用的错误信息。

让咱们看看它在遇到谬误时是如何进行提醒的。

type Person struct {    Name   string    Age    int    Emails []string    Extra  map[string]string}func decodeErrorHandle() {   input := map[string]interface{}{      "name":   123,      "age":    "bad value",      "emails": []int{1, 2, 3},   }     var result Person   err := mapstructure.Decode(input, &result)   if err != nil {      fmt.Println(err.Error())   }}

后果:

5 error(s) decoding:* 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'* 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'* 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'* 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'* 'Name' expected type 'string', got unconvertible type 'int', value: '123'

这里的错误信息会通知咱们每个字段的信息,以及字段内的值应如何示意,从而能够领导咱们无效的解决问题。

总结

上述例子展现了 mapstructure 在无效解决理论问题、提供实用解决方案和节俭开发精力方面的弱小能力。

不过,从源码角度来看,该库显然宽泛采纳了反射技术,可能会在某些非凡状况下带来性能问题。

因而,开发人员在将 mapstructure 纳入我的项目时,必须全面思考产品逻辑和应用场景。


你好,我是俞凡,在Motorola做过研发,当初在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。为了不便大家当前能第一工夫看到文章,请敌人们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的反对和能源,激励我继续写下去,和大家独特成长提高!

本文由mdnice多平台公布