基本的序列化

首先我们来看看Go语言中json.Marshal()(序列化)与json.Unmarshal(反序列化)的基本用法。

package main

import (
"encoding/json"
"fmt"
) type Person struct{
Name string
Age int64
Weight float64
} func main() {
p1 := Person{
Name:"Negan",
Age: 68,
Weight: 140.5,
} // struct -> json string
b, err := json.Marshal(p1)
if err != nil{
fmt.Printf("json.Marshal failed, err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // str:{"Name":"Negan","Age":68,"Weight":140.5} // json string -> struct
var p2 Person
err = json.Unmarshal(b, &p2)
if err != nil{
fmt.Printf("json.Unmarshal failed, err:%v\n", err)
return
}
fmt.Printf("p2:%#v\n", p2)
}

输出:

str:{"Name":"Negan","Age":68,"Weight":140.5}
p2:main.Person{Name:"Negan", Age:68, Weight:140.5}

结构体tag介绍

Tag是结构体的原信息,可以在运行的时候通过反射的机制读取出来,Tag在结构体的后方定义,由一对反引号包裹起来,具体格式如下:

`key1:"value1 key2:"value2"`

结构体tag由一个或多个键值对组成,键与值使用冒号分隔,值使用双引号括起来,同一结构体字段可以设置多个键值对tag,不同的键值对之间使用空格分隔。

使用json tag指定字段名

序列化与反序列化默认情况下使用结构体的字段名,我们可以通过给结构体字段添加tag来指定json序列生成的字段名。

type Person struct{
Name string `json:"name"` // 指定json序列化/反序列化时使用小写name
Age int64
Weight float64
}

忽略某个字段

如果现在json序列化/反序列化的时候忽略掉结构体中的某个字段,可以按如下方式在tag中添加-

type Person struct{
Name string `json:"name"` // 指定json序列化/反序列化时使用小写
Age int64
Weight float64 `json:"-"` // 指定json序列化/反序列化时忽略此字段
}

忽略空值字段

当struct中的子弹没有值时,json.Marshal()序列化的时候不会忽略这些字段,而是默认输出字段类型的零值,如intfloat类型零值都是0,string类型的零值是"",对象类型的零知识nil。如果想要在序列化时忽略这些没有值的字段时,可以在对应字段添加omitemptytag。

示例:

type User struct {
Name string `json:"name"`
Email string `json:"email"`
Hobby []string `json:"hobby"`
} func main() {
u1 := User{
Name: "Negan",
}
// struct -> json.string
b, err := json.Marshal(u1)
if err != nil{
fmt.Printf("json Marshal failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n", b) // str:{"name":"Negan","email":"","hobby":null}
}

如果想要在最终的序列化结果中去掉空值字段,可以像下面这样定义结构体:

// 在tag中添加omitempty忽略空值
// 注意这里hobby,omitempty合起来是json tag值,中间用英文逗号分隔
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
} func main() {
u1 := User{
Name: "Negan",
}
// struct -> json.string
b, err := json.Marshal(u1)
if err != nil{
fmt.Printf("json Marshal failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n", b) // str:{"name":"Negan"}
}

忽略嵌套结构体控制字段

首先来看几种结构体嵌套的示例:

type User struct{
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
Profile
} type Profile struct{
Website string `json:"site"`
Slogan string `json:"slogan"`
} func main() {
u1 := User{
Name:"Negan",
Hobby: []string{"女人","棒球"},
}
b, err := json.Marshal(u1)
if err != nil{
fmt.Printf("json.Marshal failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // str:{"name":"Negan","hobby":["女人","棒球"],"site":"","slogan":""}
}

匿名嵌套Profile时序列化后的json串为单层的,想要变成嵌套的json串,需要改为具名嵌套或定义字段tag。

type User struct{
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
// Profile Profile
Profile `json:"profile"`
} type Profile struct{
Website string `json:"site"`
Slogan string `json:"slogan"`
} func main() {
u1 := User{
Name:"Negan",
Hobby: []string{"女人","棒球"},
}
b, err := json.Marshal(u1)
if err != nil{
fmt.Printf("json.Marshal failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) //str:{"name":"Negan","hobby":["女人","棒球"],"profile":{"site":"","slogan":""}} }

想要在嵌套结构体为空值是,忽略该字段,仅添加omitempty是不够的:

type User struct{
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
// Profile Profile
Profile `json:"profile,omitempty"`
} type Profile struct{
Website string `json:"site"`
Slogan string `json:"slogan"`
} func main() {
u1 := User{
Name:"Negan",
Hobby: []string{"女人","棒球"},
}
b, err := json.Marshal(u1)
if err != nil{
fmt.Printf("json.Marshal failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) //str:{"name":"Negan","hobby":["女人","棒球"],"profile":{"site":"","slogan":""}}
}

需要使用嵌套的结构体指针:

type User struct{
Name string `json:"name"`
Email string `json:"email,omitempty"`
Hobby []string `json:"hobby,omitempty"`
// Profile Profile
*Profile `json:"profile,omitempty"`
} type Profile struct{
Website string `json:"site"`
Slogan string `json:"slogan"`
} func main() {
u1 := User{
Name:"Negan",
Hobby: []string{"女人","棒球"},
}
b, err := json.Marshal(u1)
if err != nil{
fmt.Printf("json.Marshal failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // str:{"name":"Negan","hobby":["女人","棒球"]}
}

不修改原结构体忽略空值字段

我们需要json序列化User,但是不想把密码也序列化了,又不想修改User结构体,这个时候我们就可以使用创建另外一个结构体PublicUser匿名嵌套原User,同时制定Password字段为匿名结构体指针类型,并添加omitemptytag。

type User struct {
Name string `json:"name"`
Password string `json:"password"`
} type PublicUser struct {
*User // 匿名嵌套
Password *struct{} `json:"password,omitempty"`
} func main() {
u1 := User{
Name:"Negan",
Password: "123456",
}
b,err := json.Marshal(PublicUser{User:&u1})
if err != nil{
fmt.Printf("JSON.Marshal u1 failed, err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // str:{"name":"Negan"}
}

优雅处理字符串格式的数字

有时候前端在传递来的json数据中可能会使用字符串类型的数字,这个时候可以在结构体tag中添加string来告诉json包从字符串中解析相应字段的数据。

type Card struct{
ID int64 `json:",string"` // 添加string tag
Score float64 `json:"score,string"` // 添加string tag
} func main() {
jsonStr := `{"id":"123456","score":"88.5"}`
var c1 Card
if err := json.Unmarshal([]byte(jsonStr),&c1);err!=nil{
fmt.Printf("json.Unmarsha jsonStr1 failed,err:%v\n", err)
return
}
fmt.Printf("c1:%#v\n",c1) // c1:main.Card{ID:123456, Score:88.5}
}

整数变浮点数

在Json协议中是没有整型和浮点型之分的,它们统称为number,json字符串中的数字经过Go语言中的json包反序列化之后会成为float64类型。下面的代码便延时了这个问题:

func main() {
// map[string]interface{} ->json string
var m = make(map[string]interface{},1)
m["count"] = 1 // int
b,err := json.Marshal(m)
if err != nil{
fmt.Printf("marshal failed, err:%v\n",err)
}
fmt.Printf("str:%#v\n",string(b)) // json string -> map[string]interface{}
var m2 map[string]interface{}
err = json.Unmarshal(b,&m2)
if err != nil{
fmt.Printf("unmarshal failed,err:%v\n",err)
return
}
fmt.Printf("value:%v\n",m2["count"]) // 1
fmt.Printf("type:%T\n",m2["count"]) // float64
}

这种场景下如果想要更合理的处理数字就需要使用decoder去反序列化,示例代码如下:

func main() {
// map[string]interface{} -> json string
var m = make(map[string]interface{},1)
m["count"] = 1 // int
b, err := json.Marshal(m)
if err != nil{
fmt.Printf("marshal failed,err:%v\n",err)
}
fmt.Printf("str:%#v\n",string(b)) // json string -> map[string]interface{}
var m2 map[string]interface{}
// 使用decoder方式反序列化,指定使用number类型
decoder := json.NewDecoder(bytes.NewReader(b))
decoder.UseNumber()
err = decoder.Decode(&m2)
if err != nil{
fmt.Printf("unmarshal failed,err:%v\n",err)
return
}
fmt.Printf("value:%v\n",m2["count"]) // 1
fmt.Printf("type:%T\n",m2["count"]) // json.Number
// 将m2["count"]转为json.Number之后调用Int64()方法获得int64类型的值
count,err := m2["count"].(json.Number).Int64()
if err != nil{
fmt.Printf("parse to int64 failed, err:%v\n",err)
return
}
fmt.Printf("type:%T\n",int(count)) // int
}

json.Number的源码定义如下:

// A Number represents a JSON number literal.
type Number string // String returns the literal text of the number.
func (n Number) String() string { return string(n) } // Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
return strconv.ParseFloat(string(n), 64)
} // Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}

我们在处理number类型的json字段时需要先得到json.Number类型,然后根据该字段的实际类型调用Float()Int64()

自定义解析时间字段

Go语言内置的json包使用RFC3339标准中定义的时间格式,对我们序列化时间字段的时候有很多限制。

func timeFieldDemo(){
p1 := Post{
CreateTime: time.Now(),
}
b,err := json.Marshal(p1)
if err != nil{
fmt.Printf("json.Marshal p1 failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) jsonStr := `{"create_time":"2020-05-23 10:49:50"}`
var p2 Post
if err := json.Unmarshal([]byte(jsonStr),&p2); err != nil{
fmt.Printf("json.Unmarshal failed,err:%v\n",err)
return
}
fmt.Printf("p2:%#v\n",p2)
}

上面代码运行结果如下:

str:{"create_time":"2020-05-23T10:52:56.8148613+08:00"}
json.Unmarshal failed,err:parsing time ""2020-05-23 10:49:50"" as ""2006-01-02T15:04:05Z07:00"":
cannot parse " 10:49:50"" as "T"

也就是说内置的json包不识别我们常用的字符串时间格式,如2020-05-23 10:49:50

不过我们可以通过实现json.Marshaler/json.Unmarshaler接口实现自定义的时间格式解析。

type CustomTime struct{
time.Time
} var ctLayout = "2006-01-02 15:04:05" var nilTime = (time.Time{}).UnixNano() func (c *CustomTime) UnmarshalJSON(b []byte)(err error){
s := strings.Trim(string(b),"\"")
if s == "null"{
c.Time = time.Time{}
return
}
c.Time,err = time.Parse(ctLayout,s)
return
} func (c *CustomTime) MarshalJSON()([]byte,error){
if c.Time.UnixNano() == nilTime{
return []byte("null"),nil
}
return []byte(fmt.Sprintf("\"%s\"",c.Time.Format(ctLayout))),nil
} func (c *CustomTime) IsSet() bool {
return c.UnixNano() != nilTime
} type Post struct {
CreateTime CustomTime `json:"create_time"`
} func timeFiledDemo(){
p1 := Post{CreateTime: CustomTime{time.Now()}}
b,err := json.Marshal(p1)
if err != nil{
fmt.Printf("json.Marshal p1 failed, err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) jsonStr := `{"create_time":"2020-05-23 15:51:51"}`
var p2 Post
if err := json.Unmarshal([]byte(jsonStr),&p2);err !=nil{
fmt.Printf("json.Unmarshal failed,err:%v\n",err)
return
}
fmt.Printf("p2:%#v\n",p2)
}

自定义MarshalJSON和UnmarshalJSON方法

上面的那种自定义类型的方式稍显啰嗦,下面来看一种相对边界的方法。

首先需要知道的是,如果能够为某个类型实现了MarshalJSON()([]byte,error)UnmarshalJSON(b byte[])error方法,那么这个类型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)时就会使用定制的相应的方法。

type Order struct{
ID int `json:"id"`
Title string `json:"title"`
CreatedTime time.Time `json:"created_time"`
} const layout = "2006-01-02 15:04:05" // MarshalJSON 为Orderl类型实现自定义的MarshalJSON方法
func (o *Order) MarshalJSON()([]byte, error){
type TempOrder Order // 定义与Order字段一致的新类型
return json.Marshal(struct{
CreatedTime string `json:"created_time"`
* TempOrder // 避免直接签到Order进入死循环
}{
CreatedTime:o.CreatedTime.Format(layout),
TempOrder:(*TempOrder)(o),
})
} // UnmarshalJSON 为Order类型实现自定义的UnmarshalJson方法
func (o *Order) UnmarshalJSON(data []byte)error{
type TempOrder Order // 定义与Order字段一致的新类型
ot := struct{
CreatedTime string `json:"created_time"`
*TempOrder // 避免直接嵌套Order进入死循环
}{
TempOrder:(*TempOrder)(o),
}
if err := json.Unmarshal(data,&ot); err != nil{
return err
}
var err error
o.CreatedTime,err = time.Parse(layout,ot.CreatedTime)
if err != nil{
return err
}
return nil
} // 自定义序列化方法
func cuntomMethodDemo(){
o1 := Order{
ID: 123456,
Title: "《梵高先生》",
CreatedTime:time.Now(),
} // 通过自定义的MarshalJSON方法实现struct -> json string
b,err := json.Marshal(&o1)
if err != nil{
fmt.Printf("json.Marshal o1 failed,err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // 通过自定义的UnmarshalJSON
jsonStr := `{"created_time":"2020-05-23 16:36:20", "id":1234545,"title":"《山阴路的夏天》"}`
var o2 Order
if err := json.Unmarshal([]byte(jsonStr),&o2);err!=nil{
fmt.Printf("json.Unmarshal failed, err:%v\n",err)
return
}
fmt.Printf("o2:%#v\n",o2)
}

输出结果:

str:{"CreatedTime":"2020-05-23 16:41:02","id":123456,"title":"《梵高先生》","created_time":"2020-
05-23T16:41:02.5101073+08:00"}
o2:main.Order{ID:1234545, Title:"《山阴路的夏天》", CreatedTime:time.Time{wall:0x0, ext:637258485
80, loc:(*time.Location)(nil)}}

使用匿名结构体添加字段

使用内嵌结构体能够扩展结构体的字段,但是有时候我们没有必要淡出定义新的结构体,可以使用匿名结构体简化操作:

func StructDemo(){
u1 := UserInfo{
ID: 123456,
Name: "李大鹅",
} // 使用匿名结构体内嵌User并添加额外字段Token
b, err := json.Marshal(struct {
*UserInfo
Token string `json:"token"`
}{
&u1,
"91je3adkljdafa",
})
if err != nil{
fmt.Printf("json.Marsha failed, err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // str:{"id":123456,"name":"李大鹅","token":"91je3adkljdafa"}
}

使用匿名结构体组合多个结构体

同理,也可以使用匿名结构体组合多个结构体来序列化与反序列化数据:

type Comment struct{
Content string
} type User struct {
Name string `json:"name"`
Age int `json:"age"`
} func StructDemo(){
c1 := Comment{
Content: "永远不要高估自己",
}
u1 := User{
Name: "李大鹅",
Age: 28,
} // struct -> json string
b,err := json.Marshal(struct {
*Comment
*User
}{&c1,&u1})
if err != nil{
fmt.Printf("json.Marshal failed, err:%v\n",err)
return
}
fmt.Printf("str:%s\n",b) // json string -> struct
jsonStr := `{"Content":"永远不要高估自己","name":"李大鹅","age":28}`
var (
c2 Comment
u2 User
)
if err := json.Unmarshal([]byte(jsonStr),&struct {
*Comment
* User
}{&c2,&u2});err != nil{
fmt.Printf("json.Unmarshal failed,err:%v\n",err)
return
}
fmt.Printf("c2:%#v u2:%#v\n",c2,u2)
}

输出结果:

str:{"Content":"永远不要高估自己","name":"李大鹅","age":28}
c2:main.Comment{Content:"永远不要高估自己"} u2:main.User{Name:"李大鹅", Age:28}

处理不确定层级的json

如果json串没有固定的格式导致不好定义与其相对应的结构体时,我们可以使用json.RawMessage原始字节数据保存下来。

type sendMsg struct {
User string `json:"user"`
Msg string `json:"msg"`
} func rawMessageDemo(){
jsonStr := `{"sendMsg":{"user":"李大鹅","msg":"永远不要高估自己"},"say":"hello"}`
// 定义一个map,value类型为json.RawMessage,方便后续更灵活地处理
var data map[string]json.RawMessage
if err := json.Unmarshal([]byte(jsonStr),&data);err != nil{
fmt.Printf("json.Unmarshal jsonStr failed,err:%v\n",err)
return
}
var msg sendMsg
if err := json.Unmarshal(data["sendMsg"],&msg);err!=nil{
fmt.Printf("json.Unmarshal failed,err:%v\n",err)
return
}
fmt.Printf("msg:%#v\n",msg) // msg:main.sendMsg{User:"李大鹅", Msg:"永远不要高估自己"}
}

Golang操作Json的更多相关文章

  1. 48 【golang】json的效率

    本文将主要做如下几方面的测试: 1,构造一个[100]struct的数组,然后来测试它的json编码后的字符串 或者([]byte),首先关心它的功能是否正常: 2,在很早之前,我们在使用golang ...

  2. Golang 处理 Json(二):解码

    golang 编码 json 还比较简单,而解析 json 则非常蛋疼.不像 PHP 一句 json_decode() 就能搞定.之前项目开发中,为了兼容不同客户端的需求,请求的 content-ty ...

  3. 一分钟上手, 让 Golang 操作数据库成为一种享受

    gorose, 最风骚的 go orm, 拥有链式操作, 开箱即用, 一分钟上手等八大风骚, 让 golang 操作数据库成为一种享受, 妈妈再也看不到我处理数据的痛苦了, 下面就来为大家一一讲解 g ...

  4. Golang的json包

    encoding/json encoding/json是官方提供的标准json, 实现RFC 7159中定义的JSON编码和解码.使用的时候需要预定义struct,原理是通过reflection和in ...

  5. Golang解析json的几种方法

    Golang解析json的几种方法 概要 使用Golang调用其它平台API接口时总会被多层的json串给恶心到,我记录一下自己解析json的几种方法. 一.自带的json包 func JsonUnm ...

  6. 让C#可以像Javascript一样操作Json

    Json的简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使用了 ...

  7. JavaScript操作JSON的方法总结,JSON字符串转换为JSON对象

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式.同时,JSON是 JavaScript 原生格式,这意 ...

  8. C# 技巧(3) C# 操作 JSON

    RestAPI中, 经常需要操作json字符串,  需要把json字符串"反序列化"成一个对象, 也需要把一个对象"序列化"成一字符串. C# 操作json, ...

  9. JS操作JSON总结

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式.同时,JSON是 JavaScript 原生格式,这意 ...

  10. jsoncpp操作 json

    jsoncpp操作 json 博客分类: c/c++ object-c 之 iphone   #include <iostream> //#include "json/json. ...

随机推荐

  1. Vue3+TS项目无法识别自动导入提示

    遇到问题 在写 Vue3 + TS 项目的时候,经常遇到写完一个新方法后,在组件使用的时候无法自动识别. 解决方案 Volar: Restart Vue Server 重新启动 Vue 服务

  2. deepseek内网离线部署手册

    前言 在当下 AI 浪潮汹涌的时代,DeepSeek 以其卓越的性能和出色的表现,迅速成为了众多专业人士和科技爱好者热议的焦点工具.在众多AI大模型的比拼中,DeepSeek 展现出了优越的实力.然而 ...

  3. 办公自动化-批量更新tar包内文件

    最近工作有点忙,学习的时间也少了,为了提高工作效率,有时候我们需要自己写一些提高办公处理效率给的工具或者脚本或者程序. 比如,我目前遇到的一个事项,需要更新很多个tar包文件,把tar包内的某个文件替 ...

  4. halcon 入门教程(一) 预处理图像 (图像平滑,图像增强,二值化,形态学分析)

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/18779326 本来今天想写一下halcon深度学习教程(三)目标检测的,不过今天有显卡的那台电 ...

  5. Linux下磁盘分区调整(在不使用LVM的情况下)

    当硬盘分区不使用LVM的时候,将不能使用lvresize等指令调整 在调整分区之前,先来了解一下当前的磁盘分区信息1:使用Fdisk指令查看后发现sda1的分区有195309568个Sectors(扇 ...

  6. dotnet 源代码生成器分析器入门

    本文将带领大家入门 dotnet 的 SourceGenerator 源代码生成器技术,期待大家阅读完本文能够看懂理解和编写源代码生成器和分析器 恭喜你看到了本文,进入到 C# dotnet 的深水区 ...

  7. 一文速通Python并行计算:04 Python多线程编程-多线程同步(上)—基于条件变量、事件和屏障

    一文速通 Python 并行计算:04 Python 多线程编程-多线程同步(下)-基于条件变量.事件和屏障 摘要: 本文介绍了 Python 多线程同步的三种机制:条件变量(Condition).事 ...

  8. 【硬件】认识和选购4K画质的显卡

    2.6 认识和选购4K画质的显卡 显卡一般是一块独立的电路板,插在主板上接收由主机发出的控制显示系统工作的指令和显示内容的数字信号,然后通过输出模拟(或数字)信号控制显示器显示各种字符和图形,它和显示 ...

  9. 【Java】关键字的使用

    java中有很多的关键字,他们的使用让Java语言变得更加灵活.易用,下面将介绍Java中最常用的几个关键字并说明其用法. 一.关键字:return--跳出 使用在方法体中,用于:① 结束方法② 针对 ...

  10. 【Git】在 Idea 中使用 Git

    在 Idea 中使用 Git 1 安装 Git 核心程序 根据自己的电脑操作系统从 Git 官网 https://git-scm.com/ 下载对应的 Git 核心程序. 以 git-2.21.0 为 ...