当前位置: 首页 > 工具软件 > json.c > 使用案例 >

go语言:json转换总结(json.Unmarshal、json.marshal)

艾俊晖
2023-12-01

本篇文章主要是本人在学习json与结构体转换过程中的一些摘抄笔记,总结在这里,复习使用。

Json(Javascript Object Nanotation)是一种数据交换格式,常用于前后端数据传输。任意一端将数据转换成json 字符串,另一端再将该字符串解析成相应的数据结构,如string类型,strcut对象等。

1. 下面是四种json转为结构体

  • 1. 普通JSON
package main

import (
   "encoding/json"
   "fmt"
)

// Actress 女演员
type Actress struct {
   Name       string
   Birthday   string
   BirthPlace string
   Opus       []string
}

func main() {
   // 普通JSON
   // 因为json.UnMarshal() 函数接收的参数是字节切片
   // 所以需要把JSON字符串转换成字节切片。   
   jsonData := []byte(`{
      "name":"迪丽热巴",
      "birthday":"1992-06-03",
      "birthPlace":"新疆乌鲁木齐市",
      "opus":[
         "《阿娜尔罕》",
         "《逆光之恋》",
         "《克拉恋人》"
      ]
   }`)

   var actress Actress
   err := json.Unmarshal(jsonData, &actress)
   if err != nil {
      fmt.Println("error:", err)
      return
   }
   fmt.Printf("姓名:%s\n", actress.Name)
   fmt.Printf("生日:%s\n", actress.Birthday)
   fmt.Printf("出生地:%s\n", actress.BirthPlace)
   fmt.Println("作品:")
   for _, val := range actress.Opus {
      fmt.Println("\t", val)
   }
}

结果

姓名:迪丽热巴
生日:1992-06-03
出生地:新疆乌鲁木齐市
作品:
         《阿娜尔罕》
         《逆光之恋》
         《克拉恋人》
  • 2. JSON内嵌普通JSON
package main

import (
   "encoding/json"
   "fmt"
)
// Opus 作品
type Opus struct {
   Date string
   Title string
}
// Actress 女演员
type Actress struct {
   Name       string
   Birthday   string
   BirthPlace string
   Opus       Opus
}

func main () {
   // JSON嵌套普通JSON
   jsonData := []byte(`{
      "name":"迪丽热巴",
      "birthday":"1992-06-03",
      "birthPlace":"新疆乌鲁木齐市",
      "opus": {
         "Date":"2013",
         "Title":"《阿娜尔罕》"
      }
   }`)
   var actress Actress
   err := json.Unmarshal(jsonData, &actress)
   if err != nil {
      fmt.Println("error:", err)
      return
   }
   fmt.Printf("姓名:%s\n", actress.Name)
   fmt.Printf("生日:%s\n", actress.Birthday)
   fmt.Printf("出生地:%s\n", actress.BirthPlace)
   fmt.Println("作品:")
fmt.Printf("\t%s:%s", actress.Opus.Date,   actress.Opus.Title)}

结果

姓名:迪丽热巴
生日:1992-06-03
出生地:新疆乌鲁木齐市
作品:
        2013:《阿娜尔罕》
  • 3.JSON内嵌数组JSON
package main

import (
   "encoding/json"
   "fmt"
)

type Opus struct {
   Date string
   Title string
}
type Actress struct {
   Name string
   Birthday string
   BirthPlace string
   Opus []Opus
}

func main () {
   // JSON嵌套数组JSON
   jsonData := []byte(`{
      "name":"迪丽热巴",
      "birthday":"1992-06-03",
      "birthPlace":"新疆乌鲁木齐市",
      "opus":[
         {
            "date":"2013",
            "title":"《阿娜尔罕》"
         },
         {
            "date":"2014",
            "title":"《逆光之恋》"
         },
         {
            "date":"2015",
            "title":"《克拉恋人》"
         }
      ]
   }`)
   var actress Actress
   err := json.Unmarshal(jsonData, &actress)
   if err != nil {
      fmt.Println("error:", err)
      return
   }
   fmt.Printf("姓名:%s\n", actress.Name)
   fmt.Printf("生日:%s\n", actress.Birthday)
   fmt.Printf("出生地:%s\n", actress.BirthPlace)
   fmt.Println("作品:")
   for _, val := range actress.Opus {
      fmt.Printf("\t%s - %s\n", val.Date, val.Title)
   }
}

结果

姓名:迪丽热巴
生日:1992-06-03
出生地:新疆乌鲁木齐市
作品:
        2013 - 《阿娜尔罕》
        2014 - 《逆光之恋》
        2015 - 《克拉恋人》
  • 4.JSON内嵌具有动态Key的JSON
package main

import (
   "encoding/json"
   "fmt"
)

// Opus 作品
type Opus struct {
   Type string
   Title string
}
// Actress 女演员
type Actress struct {
   Name string
   Birthday string
   BirthPlace string
   Opus map[string]Opus
}

func main () {
   jsonData := []byte(`{
      "name":"迪丽热巴",
      "birthday":"1992-06-03",
      "birthPlace":"新疆乌鲁木齐市",
      "opus":{
         "2013":{
            "Type":"近代革命剧",
            "Title":"《阿娜尔罕》"
         },
         "2014":{
            "Type":"奇幻剧",
            "Title":"《逆光之恋》"
         },
         "2015":{
            "Type":"爱情剧",
            "Title":"《克拉恋人》"
         }
      }
   }`)
   var actress Actress
   err := json.Unmarshal(jsonData, &actress)
   if err != nil {
      fmt.Println("error:", err)
      return
   }
   fmt.Printf("姓名:%s\n", actress.Name)
   fmt.Printf("生日:%s\n", actress.Birthday)
   fmt.Printf("出生地:%s\n", actress.BirthPlace)
   fmt.Println("作品:")
   for index, value := range actress.Opus {
      fmt.Printf("\t日期:%s\n", index)
      fmt.Printf("\t\t分类:%s\n", value.Type)
      fmt.Printf("\t\t标题:%s\n", value.Title)
   }
}

结果

姓名:迪丽热巴
生日:1992-06-03
出生地:新疆乌鲁木齐市
作品:
        日期:2013
                分类:近代革命剧
                标题:《阿娜尔罕》
        日期:2014
                分类:奇幻剧
                标题:《逆光之恋》
        日期:2015
                分类:爱情剧
                标题:《克拉恋人》

转载自:Golang语言之JSON解码函数Unmarshal


2. 另一篇很好的文章是:Go的json解析:Marshal与Unmarshal

摘抄笔记如下:强烈建议去看原文,下面是我的一些摘抄,以便复习使用。

  • 1. 将数据编码成json字符串
type Stu struct {
    Name  string `json:"name"`
    Age   int
    HIgh  bool
    sex   string
    Class *Class `json:"class"`
}

type Class struct {
    Name  string
    Grade int
}

func main() {
    //实例化一个数据结构,用于生成json字符串
    stu := Stu{
        Name: "张三",
        Age:  18,
        HIgh: true,
        sex:  "男",
    }

    //指针变量
    cla := new(Class)
    cla.Name = "1班"
    cla.Grade = 3
    stu.Class=cla

    //Marshal失败时err!=nil
    jsonStu, err := json.Marshal(stu)
    if err != nil {
        fmt.Println("生成json字符串错误")
    }

    //jsonStu是[]byte类型,转化成string类型便于查看
    fmt.Println(string(jsonStu))
}

结果是小写的key不能被json化,因为首字母小写的为私有属性,大写的为公有属性。

{"name":"张三","Age":18,"HIgh":true,"class":{"Name":"1班","Grade":3}}
  • 2. 空接口

interface{} 类型其实是个空接口,即没有方法的接口。go的每一种类型都实现了该接口。因此,任何其他类型的数据都可以赋值给interface{}类型

type Stu struct {
    Name  interface{} `json:"name"`
    Age   interface{}
    HIgh  interface{}
    sex   interface{}
    Class interface{} `json:"class"`
}

type Class struct {
    Name  string
    Grade int
}

func main() {
    //与前面的例子一样
    ......
}

3. 还有这个:Go 每日一库之 mapstructure

  1. 对于不知道类型的多源数据流的解码通常这样:

    • 采用标准的encoding/json库将数据解码为通用map[string]interface{}类型
    • 利用mapstructure库转为相应的 Go 结构体
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"github.com/mitchellh/mapstructure"
)

type Person struct {
	Name string
	Age  int
	Job  string
}

type Cat struct {
	Name  string
	Age   int
	Breed string
}
func (c *Cat) miao(){
	fmt.Println("喵喵喵~")
}

func main() {
	datas := []string{
		`{
		  "type": "person",
		  "name":"dj",
		  "age":18,
		  "job": "programmer"
		}`,
		`{
		  "type": "cat",
		  "name": "kitty",
		  "age": 1,
		  "breed": "Ragdoll"
		}`,
	}

	for _, data := range datas {
		var m map[string]interface{}
		err := json.Unmarshal([]byte(data), &m)  //转到通用接口中
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(m["type"])  
		//输出 person
		fmt.Println(m)
		//输出 map[age:18 job:programmer name:dj type:person]
		switch m["type"].(string) {   //获取key-value,接口类型转为字符出(断言)
		case "person":
			var p Person
			mapstructure.Decode(m, &p)  //转为结构体
			fmt.Println("person", p)
			//输出 person {dj 18 programmer}
		case "cat":
			var cat Cat
			mapstructure.Decode(m, &cat)
			fmt.Println("cat", cat)
			cat.miao()
		}
	}
}

结果

省略...
map[age:1 breed:Ragdoll name:kitty type:cat]
cat {kitty 1 Ragdoll}
喵喵喵~

上面的json中首字母大小写都有,所以mapstructure处理字段映射是大小写不敏感的。

  1. 结构体的嵌套被认为是拥有该结构体名字的另一个字段
    • 首先看三种继承方式
// 方式一
type Friend struct {
	Person
}

// 方式二
type Friend struct {
	Person Person
}

// 将结构体字段提到父结构中
type Friend struct {
	Person `mapstructure:",squash"`
}

例子

package main

import (
	"encoding/json"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"log"
)

type Person struct {
	Name string
}

type Friend1 struct {
	Person
}

type Friend2 struct {
	Person `mapstructure:",squash"`
}

func main() {
	datas := []string{`
		{ 
		  "type": "friend1",
		  "person": {
			"name":"dj"
		  }
		}`,
		`{
		  "type": "friend2",
		  "name": "dj2"
		}`,
	}

	for _, data := range datas {
		var m map[string]interface{}
		err := json.Unmarshal([]byte(data), &m)
		if err != nil {
			log.Fatal(err)
		}

		switch m["type"].(string) {
		case "friend1":
			var f1 Friend1
			mapstructure.Decode(m, &f1)
			fmt.Println("friend1", f1)

		case "friend2":
			var f2 Friend2
			mapstructure.Decode(m, &f2)
			fmt.Println("friend2", f2)
		}
	}
}

结果

friend1 {{dj}}
friend2 {{dj2}}

结构体中无对应的字段,mapstructure会忽略它。在结构体中设置mapstructure:",remain"标签。未映射的值就会添加到这个字段中。注意,这个字段的类型只能为map[string]interface{}或map[interface{}]interface{}。

package main

import (
	"encoding/json"
	"fmt"
	"github.com/mitchellh/mapstructure"
	"log"
)

type Person struct {
	Name  string
	Age   int
	Job   string
	Other map[string]interface{} `mapstructure:",remain"`
}

func main() {
	data := `
    { 
      "name": "dj",
      "age":18,
      "job":"programmer",
      "height":"1.8m",
      "handsome": true
    }`

	var m map[string]interface{}
	err := json.Unmarshal([]byte(data), &m)
	if err != nil {
		log.Fatal(err)
	}

	var p Person
	mapstructure.Decode(m, &p)
	fmt.Println("other", p.Other)
}

输出

other map[handsome:true height:1.8m]

逆向转换
将 结构体反向解码为map[string]interface{}。在反向解码时,我们可以为某些字段设置mapstructure:",omitempty"。这样当这些字段为默认值时,就不会出现在结构的map[string]interface{}中:

package main

import (
	"encoding/json"
	"fmt"
	"github.com/mitchellh/mapstructure"
)

type Person struct {
	Name string
	Age  int
	Job  string `mapstructure:",omitempty"`
}

func main() {
	p := &Person{
		Name: "dj",
		Age:  18,
		Job: "student",
	}
	fmt.Println("p:", *p)

	var m map[string]interface{}
	mapstructure.Decode(p, &m)

	data, _ := json.Marshal(m)
	fmt.Println(string(data))
}

输出

p: {dj 18 student}
{"":"student","Age":18,"Name":"dj"}

Metadata

mapstructure可以使用Metadata收集这些信息

Metadata只有两个导出字段:

  • Keys:解码成功的键名;
  • Unused:在源数据中存在,但是目标结构中不存在的键名。
package main

import (
	"fmt"
	"github.com/mitchellh/mapstructure"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	m := map[string]interface{}{
		"name": "dj",
		"age":  18,
		"job":  "programmer",
	}

	var p Person
	var metadata mapstructure.Metadata
	mapstructure.DecodeMetadata(m, &p, &metadata)

	fmt.Printf("keys:%#v unused:%#v\n", metadata.Keys, metadata.Unused)
}

结果

keys:[]string{"Name", "Age"} unused:[]string{"job"}

错误处理

未转载...

弱类型输入

未转载...

4. 学习参考 GO 全面解析 json tag 篇

golang中命名用驼峰,小写包外无法引用,为了在结构体转json(存储等)时得到想要的格式,通常采用tag的方式声明。

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	UserId   int    //`json:"user_id" bson:"user_id"`
	UserName string `json:"user_name" bson:"user_name"`
}

func main(){
	u := &User{UserId: 123, UserName: "along"}
	j, err := json.Marshal(u)
	if err != nil {
		fmt.Println("Marshal error!")
	}
	fmt.Println(string(j))
	fmt.Println()
}

输出,注意只有一个变成了小写下划线

{"UserId":123,"user_name":"along"}

使用反射包(reflect)中的方法来获取tag中的内容

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)
type User struct {
	UserId   int    `json:"user_id" bson:"user_id"`
	UserName string `json:"user_name" bson:"user_name"`
}

func main() {
	// 输出json格式
	u := &User{UserId: 1, UserName: "tony"}
	j, _ := json.Marshal(u)
	fmt.Println(string(j))
	// 输出内容:{"user_id":1,"user_name":"tony"}

	// 获取tag中的内容
	t := reflect.TypeOf(u)
	field := t.Elem().Field(0)
	fmt.Println(field.Tag.Get("json"))
	// 输出:user_id
	fmt.Println(field.Tag.Get("bson"))
	// 输出:user_id
}

5 . 学习的第五篇: golang中json的omitempty使用

json:"docs,omitempty"用法

package main

import (
	"encoding/json"
	"fmt"
)

type Project struct {
	Name string `json:"name"`
	Url  string `json:"url"`
	Docs string `json:"docs,omitempty"` //有就有,没有就拉到
}

func main() {
	p1 := Project{
		Name:"hello name",
		Url:"https://www.baidu.com/",
	}

	data, err := json.Marshal(p1)
	if err != nil {
		panic(err)
	}

	// Docs定义为omitempty所以不会出现Docs的字段
	fmt.Printf("%s\n", data)

	p2 := Project{
		Name:"baidu",
		Url:"https://www.baidu.com/",
		Docs:"https://www.baidu.com/",
	}

	data2, err := json.Marshal(p2)
	if err != nil {
		panic(err)
	}

	//打印出所有的字段
	fmt.Printf("%s\n", data2)
}

结果

{"name":"hello name","url":"https://www.baidu.com/"}
{"name":"baidu","url":"https://www.baidu.com/","docs":"https://www.baidu.com/"}

对比

package main

import (
	"encoding/json"
	"fmt"
)

type Project struct {
	Name string `json:"name"`
	Url  string `json:"url"`
	Docs string //`json:"docs,omitempty"` //进行对比
}

func main() {
	p1 := Project{
		Name:"hello name",
		Url:"https://www.baidu.com/",
	}

	data, err := json.Marshal(p1)
	if err != nil {
		panic(err)
	}

	// Docs定义为omitempty所以不会出现Docs的字段
	fmt.Printf("%s\n", data)

	p2 := Project{
		Name:"baidu",
		Url:"https://www.baidu.com/",
		Docs:"https://www.baidu.com/",
	}

	data2, err := json.Marshal(p2)
	if err != nil {
		panic(err)
	}

	//打印出所有的字段
	fmt.Printf("%s\n", data2)

}

输出

{"name":"hello name","url":"https://www.baidu.com/","Docs":""}
{"name":"baidu","url":"https://www.baidu.com/","Docs":"https://www.baidu.com/"}

上面有两处不同:

  • 没有json:"docs",会按照结构体里的名字进行转json。
  • 没有omitempty,会输出Docs字段。

深入理解转换

主要参考Golang 的 “omitempty” 关键字略解中的陷阱章节中主要讲:对于用 omitempty 定义的 field ,如果给它赋的值恰好等于默认空值的话,在转为 json 之后也不会输出这个 field 。

思考一个小例子

如果json少的字段,转为结构体时,结构体中有该字段,转换之后依然有。
如果json多出来的字段,转为结构体时,结构体中没有,转换之后依然没有。
这两句话就像是,在给结构体赋值,有的就赋值,没有就空着,omitempty这样写的json:"docs,omitempty",是转json时候的行为,转为docs时,Docs转为docs。

package main
import (
	"encoding/json"
	"fmt"
)

type Project struct {
	Name string `json:"name"`
	Url string `json:"url"`
	Docs string `json:"docs,omitempty"` //
}

func main() {
	//json 转结构体,为空的字段照样有
	json1 := []byte(`{"url":"https://blog.csdn.net/qq_30505673","name":"hello name","name2":"hello name"}`)

	var project Project
	err = json.Unmarshal(json1, &project)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(project.Name)
	fmt.Println(project.Url)
	fmt.Println("?")
	fmt.Println(project.Docs)
	project.Docs = "along"   //可以赋值
	fmt.Println(project.Docs)
}

输出

hello name
https://blog.csdn.net/qq_30505673
?

along

整型不能给字符串

package main
import (
	"encoding/json"
	"fmt"
)

type Project struct {
	Name string `json:"name"`
	Url  string `json:"url"`
	Docs string `json:"docs,omitempty"` //
	Num  int    `json:"num"`
}

func main() {
	//json 转结构体,为空的字段照样有
	json1 := []byte(`{"url":"https://blog.csdn.net/qq_30505673","name":"hello name","name2":"hello name","num":2}`)

	var project Project
	err := json.Unmarshal(json1, &project)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(project.Name)
	fmt.Println(project.Url)
	fmt.Println("?")
	fmt.Println(project.Docs)
	project.Docs = "along"   //可以赋值
	fmt.Println(project.Docs)
}
 类似资料: