首页 > 后端开发 > Golang > 正文

Go语言中处理json数据的方法

发布: 2020-01-11 17:14:07
转载
11865人浏览过

Go语言中处理json数据的方法

Go json包

marshal():go数据对象 -> json数据

UnMarshal():Json数据 -> Go数据对象

func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
登录后复制

构建json数据

Marshal()和MarshalIndent()函数可以将数据封装成json数据。

1、struct、slice、array、map都可以转换成json

2、struct转换成json的时候,只有字段首字母大写的才会被转换

立即学习go语言免费学习笔记(深入)”;

3、map转换的时候,key必须为string

4、封装的时候,如果是指针,会追踪指针指向的对象进行封装

例如:

有一个struct结构:

type Post struct {
    Id      int
    Content string
    Author  string
}
登录后复制

这个结构表示博客文章类型,有文章ID,文章内容,文章的提交作者。这没什么可说的,唯一需要指明的是:它是一个struct,struct可以封装(编码)成JSON数据。

要将这段struct数据转换成json,只需使用Marshal()即可。如下:

post := &Post{1, "Hello World", "userA"}
b, err := json.Marshal(post)
if err != nil {
    fmt.Println(nil)
}
登录后复制

Marshal()返回的是一个[]byte类型,现在变量b就已经存储了一段[]byte类型的json数据,可以输出它:

fmt.Println(string(b))
登录后复制

结果:

{"Id":1,"Content":"Hello World","Author":"userA"}
登录后复制

可以在封装成json的时候进行"美化",使用MarshalIndent()即可自动添加前缀(前缀字符串一般设置为空)和缩进:

c,err := json.MarshalIndent(post,"","\t")
if err != nil {
    fmt.Println(nil)
}
fmt.Println(string(c))
登录后复制

结果:

{
    "Id": 1,
    "Content": "Hello World",
    "Author": "userA"
}
登录后复制

除了struct,array、slice、map结构都能解析成json,但是map解析成json的时候,key必须只能是string,这是json语法要求的。

例如:

// slice -> json
s := []string{"a", "b", "c"}
d, _ := json.MarshalIndent(s, "", "\t")
fmt.Println(string(d))

// map -> json
m := map[string]string{
    "a":"aa",
    "b":"bb",
    "c":"cc",
}
e,_ := json.MarshalIndent(m,"","\t")
fmt.Println(string(e))
登录后复制

返回结果:

[
    "a",
    "b",
    "c"
]
{
    "a": "aa",
    "b": "bb",
    "c": "cc"
}
登录后复制

使用struct tag辅助构建json

struct能被转换的字段都是首字母大写的字段,但如果想要在json中使用小写字母开头的key,可以使用struct的tag来辅助反射。

例如,Post结构增加一个首字母小写的字段createAt。

type Post struct {
    Id      int      `json:"ID"`
    Content string   `json:"content"`
    Author  string   `json:"author"`
    Label   []string `json:"label"`
}


postp := &Post{
    2,
    "Hello World",
    "userB",
    []string{"linux", "shell"},
    }

p, _ := json.MarshalIndent(postp, "", "\t")
fmt.Println(string(p))
登录后复制

结果:

{
    "ID": 2,
    "content": "Hello World",
    "author": "userB",
    "label": [
        "linux",
        "shell"
    ]
}
登录后复制

使用struct tag的时候,几个注意点:

1、tag中标识的名称将称为json数据中key的值

2、tag可以设置为`json:"-"`来表示本字段不转换为json数据,即使这个字段名首字母大写

如果想要json key的名称为字符"-",则可以特殊处理`json:"-,"`,也就是加上一个逗号

3、如果tag中带有,omitempty选项,那么如果这个字段的值为0值,即false、0、""、nil等,这个字段将不会转换到json中

4、如果字段的类型为bool、string、int类、float类,而tag中又带有,string选项,那么这个字段的值将转换成json字符串

例如:

type Post struct {
    Id      int      `json:"ID,string"`
    Content string   `json:"content"`
    Author  string   `json:"author"`
    Label   []string `json:"label,omitempty"`
}
登录后复制

解析json数据到struct(结构已知)

json数据可以解析到struct或空接口interface{}中(也可以是slice、map等)。理解了上面构建json时的tag规则,理解解析json就很简单了。

Find JSON Path Online
Find JSON Path Online

Easily find JSON paths within JSON objects using our intuitive Json Path Finder

Find JSON Path Online 30
查看详情 Find JSON Path Online

例如,下面是一段json数据:

{
    "id": 1,
    "content": "hello world",
    "author": {
        "id": 2,
        "name": "userA"
    },
    "published": true,
    "label": [],
    "nextPost": null,
    "comments": [{
            "id": 3,
            "content": "good post1",
            "author": "userB"
        },
        {
            "id": 4,
            "content": "good post2",
            "author": "userC"
        }
    ]
}
登录后复制

分析下这段json数据:

1、顶层的大括号表示是一个匿名对象,映射到Go中是一个struct,假设这个struct名称为Post

2、顶层大括号里的都是Post结构中的字段,这些字段因为都是json数据,所以必须都首字母大写,同时设置tag,tag中的名称小写

3、其中author是一个子对象,映射到Go中是另一个struct,在Post中这个字段的名称为Author,假设名称和struct名称相同,也为Author

4、label是一个数组,映射到Go中可以是slice,也可以是array,且因为json array为空,所以Go中的slice/array类型不定,比如可以是int,可以是string,也可以是interface{},对于这里的示例来说,我们知道标签肯定是string

5、nextPost是一个子对象,映射到Go中是一个struct,但因为json中这个对象为null,表示这个对象不存在,所以无法判断映射到Go中struct的类型。但对此处的示例来说,是没有下一篇文章,所以它的类型应该也是Post类型

6、comment是子对象,且是数组包围的,映射到Go中,是一个slice/array,slice/array的类型是一个struct

分析之后,对应地去构建struct和struct的tag就很容易了。如下,是根据上面分析构建出来的数据结构:

type Post struct {
    ID        int64         `json:"id"`       
    Content   string        `json:"content"`  
    Author    Author        `json:"author"`   
    Published bool          `json:"published"`
    Label     []string      `json:"label"`    
    NextPost  *Post         `json:"nextPost"` 
    Comments  []*Comment    `json:"comments"` 
}

type Author struct {
    ID   int64  `json:"id"`  
    Name string `json:"name"`
}

type Comment struct {
    ID      int64  `json:"id"`     
    Content string `json:"content"`
    Author  string `json:"author"` 
}
登录后复制

注意,前面在介绍构建json数据的时候说明过,指针会进行追踪,所以这里反推出来的struct中使用指针类型是没问题的。

于是,解析上面的json数据到Post类型的对象中,假设这个json数据存放在a.json文件中。代码如下:

func main() {
    // 打开json文件
    fh, err := os.Open("a.json")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer fh.Close()
    // 读取json文件,保存到jsonData中
    jsonData, err := ioutil.ReadAll(fh)
    if err != nil {
        fmt.Println(err)
        return
    }
    
    var post Post
    // 解析json数据到post中
    err = json.Unmarshal(jsonData, &post)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(post)
}
登录后复制

输出结果:

{1 hello world {2 userA} true [] <nil> [0xc042072300 0xc0420723c0]}
登录后复制

也许你已经感受到了,从json数据反推算struct到底有多复杂,虽然逻辑不难,但如果数据复杂一点,这是件非常恶心的事情。所以,使用别人写好的工具来自动转换吧。本文后面有推荐json到数据结构的自动转换工具。

解析json到interface(结构未知)

上面是已知json数据结构的解析方式,如果json结构是未知的或者结构可能会发生改变的情况,则解析到struct是不合理的。这时可以解析到空接口interface{}或map[string]interface{}类型上,这两种类型的结果是完全一致的。

解析到interface{}上时,Go类型和JSON类型的对应关系如下

  JSON类型             Go类型                
---------------------------------------------
JSON objects    <-->  map[string]interface{} 
JSON arrays     <-->  []interface{}          
JSON booleans   <-->  bool                   
JSON numbers    <-->  float64                
JSON strings    <-->  string                 
JSON null       <-->  nil
登录后复制

例如:

func main() {
    // 读取json文件
    fh, err := os.Open("a.json")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer fh.Close()
    jsonData, err := ioutil.ReadAll(fh)
    if err != nil {
        fmt.Println(err)
        return
    }
    
    // 定义空接口接收解析后的json数据
    var unknown interface{}
    // 或:map[string]interface{} 结果是完全一样的
    err = json.Unmarshal(jsonData, &unknown)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(unknown)
}
登录后复制

输出结果:

map[nextPost:<nil> comments:[map[id:3 content:good post1
author:userB] map[id:4 content:good post2 author:userC]]
id:1 content:hello world author:map[id:2 name:userA] published:true label:[]]
登录后复制

上面将输出map结构。这是显然的,因为类型对应关系中已经说明了,json object解析到Go interface的时候,对应的是map结构。如果将上面输出的结构进行一下格式化,得到的将是类似下面的结构:

map[
    nextPost:<nil>
    comments:[
        map[
            id:3
            content:good post1
            author:userB
        ]
        map[
            id:4
            content:good post2
            author:userC
        ]
    ]
    id:1
    content:hello world
    author:map[
        id:2
        name:userA
    ]
    published:true
    label:[]
]
登录后复制

现在,可以从这个map中去判断类型、取得对应的值。但是如何判断类型?可以使用类型断言:

func main() {
    // 读取json数据
    fh, err := os.Open("a.json")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer fh.Close()
    jsonData, err := ioutil.ReadAll(fh)
    if err != nil {
        fmt.Println(err)
        return
    }
    
    // 解析json数据到interface{}
    var unknown interface{}
    err = json.Unmarshal(jsonData, &unknown)
    if err != nil {
        fmt.Println(err)
        return
    }

    // 进行断言,并switch匹配
    m := unknown.(map[string]interface{})
    for k, v := range m {
        switch vv := v.(type) {
        case string:
            fmt.Println(k, "type: string\nvalue: ", vv)
            fmt.Println("------------------")
        case float64:
            fmt.Println(k, "type: float64\nvalue: ", vv)
            fmt.Println("------------------")
        case bool:
            fmt.Println(k, "type: bool\nvalue: ", vv)
            fmt.Println("------------------")
        case map[string]interface{}:
            fmt.Println(k, "type: map[string]interface{}\nvalue: ", vv)
            for i, j := range vv {
                fmt.Println(i,": ",j)
            }
            fmt.Println("------------------")
        case []interface{}:
            fmt.Println(k, "type: []interface{}\nvalue: ", vv)
            for key, value := range vv {
                fmt.Println(key, ": ", value)
            }
            fmt.Println("------------------")
        default:
            fmt.Println(k, "type: nil\nvalue: ", vv)
            fmt.Println("------------------")
        }
    }
}
登录后复制

结果如下:

comments type: []interface{}
value:  [map[id:3 content:good post1 author:userB] map[author:userC id:4 content:good post2]]
0 :  map[id:3 content:good post1 author:userB]
1 :  map[id:4 content:good post2 author:userC]
------------------
id type: float64
value:  1
------------------
content type: string
value:  hello world
------------------
author type: map[string]interface{}
value:  map[id:2 name:userA]
name :  userA
id :  2
------------------
published type: bool
value:  true
------------------
label type: []interface{}
value:  []
------------------
nextPost type: nil
value:  <nil>
------------------
登录后复制

可见,从interface中解析非常复杂,而且可能因为嵌套结构而导致无法正确迭代遍历。这时候,可以使用第三方包simplejson,见后文。

解析、创建json流

除了可以直接解析、创建json数据,还可以处理流式数据。

1、type Decoder解码json到Go数据结构

2、ype Encoder编码Go数据结构到json

例如:

const jsonStream = `
    {"Name": "Ed", "Text": "Knock knock."}
    {"Name": "Sam", "Text": "Who's there?"}
    {"Name": "Ed", "Text": "Go fmt."}
    {"Name": "Sam", "Text": "Go fmt who?"}
    {"Name": "Ed", "Text": "Go fmt yourself!"}
`
type Message struct {
    Name, Text string
}
dec := json.NewDecoder(strings.NewReader(jsonStream))
for {
    var m Message
    if err := dec.Decode(&m); err == io.EOF {
        break
    } else if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s: %s\n", m.Name, m.Text)
}
登录后复制

输出:

Ed: Knock knock.
Sam: Who's there?
Ed: Go fmt.
Sam: Go fmt who?
Ed: Go fmt yourself!
登录后复制

再例如,从标准输入读json数据,解码后删除名为Name的元素,最后重新编码后输出到标准输出。

func main() {
    dec := json.NewDecoder(os.Stdin)
    enc := json.NewEncoder(os.Stdout)
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Name" {
                delete(v, k)
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}
登录后复制

更多go语言知识请关注PHP中文网go语言教程栏目。

以上就是Go语言中处理json数据的方法的详细内容,更多请关注php中文网其它相关文章!

相关标签:
最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:博客园网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号