Golang动态高效JSON解析技巧

绣橘
• 阅读 289
JSON如今广泛用于配置和通信协议,但由于其定义的灵活性,很容易传递错误数据。本文介绍了如何使用mapstructure工具实现动态灵活的JSON数据解析,在牺牲一定性能的前提下,有效提升开发效率和容错能力。原文: Efficient JSON Data Handling: Dynamic Parsing Tips in Golang

打造无缝 Golang 体验,探索动态 JSON 解析技术,实现最佳开发实践。

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多平台发布

点赞
收藏
评论区
推荐文章
liuzhen007 liuzhen007
4年前
Android如何解析json字符串
前言上一篇文章介绍了服务器用Golang如何解析json字符串,今天我们来看看Android客户端是如何解析json字符串的。正文Golang如何解析post请求中的json字符串(https://www.helloworld.net/p/O917HGeiALU2D)使用java语句如何正确解析json字符串呢?举一个例子,假如我们想从rtc_i
Wesley13 Wesley13
3年前
JSON的简单认识
1前言JSON(JavaScript Object Notation):JavaScript对象表示法。其是一种轻量级的数据交换格式,简洁和清晰的层次结构使得其成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。通常用于与服务端交换数据。如心知天气服务器
Stella981 Stella981
3年前
C# JToken类的使用,实现解析动态json数据、遍历、查找
在原来解析json数据是,一般都是用反序列化来实现json数据的解读,这需要首先知道json数据的结构并且建立相应的类才能反序列化,一旦遇到动态的json数据,这种方法就不使用。为了解决动态解析json数据,微软有个Newtonsoft.Json封装类十分好用,里面的JToken直接实现ling查询。将json字符串解析为JToken,
Wesley13 Wesley13
3年前
GSON搞定任何JSON数据
Gson介绍:GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库。可以将一个Json字符转成一个Java对象,或者将一个Java转化为Json字符串。特点:a、快速、高效    b、代码量少、简洁c、面向对象d、数据传递和解析方便Gson的p
Stella981 Stella981
3年前
Groovy常用编程知识点简明教程
概述Groovy是一门基于JVM的动态类型语言,可以与Java平台几乎无缝融合(与Java代码一样编译成字节码)。使用Groovy,可以增强Java应用的灵活性和可扩展性,提升开发效率。使用GroovySpock组合也更容易编写简洁易懂的单测。熟练使用Groovy编程是很有益处的。本文针对已有一定java开发
Stella981 Stella981
3年前
Gson的学习与使用
Gson介绍:GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库。可以将一个Json字符转成一个Java对象,或者将一个Java转化为Json字符串。特点:a、快速、高效b、代码量少、简洁c、面向对象d、数据传递和解析方便Gson的pom依赖:<d
Wesley13 Wesley13
3年前
C++构造和解析JSON
JSON是一种轻量级的数据交互格式,易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率,实际项目中经常用到,相比xml有很多优点,问问度娘,优点一箩筐。第三方库json解析选用jsoncpp作为第三方库,jsoncpp使用广泛,c开发首选。jsoncpp目前已经托管到了github上,地址:https://git
Stella981 Stella981
3年前
Golang
golang官方为我们提供了标准的json解析库–encoding/json,大部分情况下,使用它已经够用了。不过这个解析包有个很大的问题–性能。它不够快,如果我们开发高性能、高并发的网络服务就无法满足,这时就需要高性能的json解析库,目前性能比较高的有jsoniterator和easyjson。现在我们需要引进一个高性能的json解析库
Stella981 Stella981
3年前
Golang 入门系列(九) 如何读取YAML,JSON,INI等配置文件
实际项目中,读取相关的系统配置文件是很常见的事情。今天就来说一说,Golang是如何读取YAML,JSON,INI等配置文件的。1\.json使用JSON应该比较熟悉,它是一种轻量级的数据交换格式。层次结构简洁清晰,易于阅读和编写,同时也易于机器解析和生成。  1.创建conf.json:{
使用Python和XPath解析动态JSON数据
JSON动态数据在Python中扮演着重要的角色,为开发者提供了处理实时和灵活数据的能力。Python作为一种强大的编程语言,提供了丰富的工具和库来处理动态JSON数据使得解析和处理动态JSON数据变得简单和高效。例如,使用内置的json模块,我们可以轻松
小白学大数据 小白学大数据
8个月前
网络延迟对Python爬虫速度的影响分析
Python爬虫因其强大的数据处理能力和灵活性而被广泛应用于数据抓取和网络信息收集。然而,网络延迟是影响爬虫效率的重要因素之一。本文将深入探讨网络延迟对Python爬虫速度的影响,并提供相应的代码实现过程,以帮助开发者优化爬虫性能。网络延迟的定义与影响网络