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

Freigeben: 2020-01-11 17:14:07
nach vorne
11524 Leute haben es durchsucht

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

Go json包

Marshal():Go数据对象 -> json数据

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

func Marshal(v interface{}) ([]byte, error) func Unmarshal(data []byte, v interface{}) error
Nach dem Login kopieren

构建json数据

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

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

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

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

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

例如:

有一个struct结构:

type Post struct { Id int Content string Author string }
Nach dem Login kopieren

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

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

post := &Post{1, "Hello World", "userA"} b, err := json.Marshal(post) if err != nil { fmt.Println(nil) }
Nach dem Login kopieren

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

fmt.Println(string(b))
Nach dem Login kopieren

结果:

{"Id":1,"Content":"Hello World","Author":"userA"}
Nach dem Login kopieren

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

c,err := json.MarshalIndent(post,"","\t") if err != nil { fmt.Println(nil) } fmt.Println(string(c))
Nach dem Login kopieren

结果:

{ "Id": 1, "Content": "Hello World", "Author": "userA" }
Nach dem Login kopieren

除了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))
Nach dem Login kopieren

返回结果:

[ "a", "b", "c" ] { "a": "aa", "b": "bb", "c": "cc" }
Nach dem Login kopieren

使用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))
Nach dem Login kopieren

结果:

{ "ID": 2, "content": "Hello World", "author": "userB", "label": [ "linux", "shell" ] }
Nach dem Login kopieren

使用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"` }
Nach dem Login kopieren

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

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

例如,下面是一段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" } ] }
Nach dem Login kopieren

分析下这段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"` }
Nach dem Login kopieren

注意,前面在介绍构建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) }
Nach dem Login kopieren

输出结果:

{1 hello world {2 userA} true []  [0xc042072300 0xc0420723c0]}
Nach dem Login kopieren

也许你已经感受到了,从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
Nach dem Login kopieren

例如:

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) }
Nach dem Login kopieren

输出结果:

map[nextPost: 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:[]]
Nach dem Login kopieren

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

map[ nextPost: 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:[] ]
Nach dem Login kopieren

现在,可以从这个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("------------------") } } }
Nach dem Login kopieren

结果如下:

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:  ------------------
Nach dem Login kopieren

可见,从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) }
Nach dem Login kopieren

输出:

Ed: Knock knock. Sam: Who's there? Ed: Go fmt. Sam: Go fmt who? Ed: Go fmt yourself!
Nach dem Login kopieren

再例如,从标准输入读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) } } }
Nach dem Login kopieren

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

Das obige ist der detaillierte Inhalt vonGo语言中处理json数据的方法. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:cnblogs.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!