首页 > 其他分享 >go web的json系列化

go web的json系列化

时间:2024-10-06 20:25:35浏览次数:1  
标签:web http string err JSON json go 序列化

  打开APP    

golang WEB应用【2】:json数据处理应用 原创

https://blog.csdn.net/loo_Charles_ool/article/details/138916161原地址

2024-05-15 17:33:48

阅读量980

收藏28

 32赞

一叶萩Charles 

码龄7年

关注

 

文章目录

 

 

json数据处理应用

JSON格式简介

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于ECMAScript(欧洲计算机制造商协会指定的一种脚本程序标准)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得JSON成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成

JSON格式是键/值对的集合。在不同的编程语言中,这些键/值对被理解为对象(object)纪录(record)结构(struct)字典(dictionary)哈希表(hash table)有键列表(keyed list)或者关联数组(associative array

golang 中主要理解为结构(struct)接口(interface)

JSON的语法规则包括:

  • 数据在名称/值对中
  • 数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组

JSON数据的结构有两种:

  1. 对象(Object):一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个名称后跟一个“:”(冒号);“‘名称/值’对”之间使用“,”(逗号)分隔。
  2. 数组(Array):值的有序集合。一个数组以"[“(左中括号)开始,”]"(右中括号)结束。值之间使用“,”(逗号)分隔。

JSON的值可以是以下几种类型:

  • 字符串(String):以双引号包围的Unicode字符序列。
  • 数值(Number):整数或浮点数。
  • 对象(Object):上面描述的JSON对象。
  • 数组(Array):上面描述的JSON数组。
  • 布尔值(Boolean):true或false。
  • 空值(Null):表示一个空值。

示例:

{
  "name": "张三",
  "age": 30,
  "isMarried": false,
  "children": null,
  "hobbies": ["reading", "traveling", "sports"],
  "address": {
    "city": "北京",
    "street": "长安街",
    "postcode": "100000"
  }
}

 

在这个例子中,我们定义了一个包含多种类型值的对象,其中包含了姓名、年龄、婚姻状态、爱好、子女和地址等信息

Go语言中的JSON序列化和反序列化

在Go语言中,encoding/json 标准库提供了JSON序列化和反序列化的功能。序列化(Serialization)是指将Go语言中的结构体(struct)或其他数据类型转换为JSON格式的字符串,而反序列化(Deserialization)则是将JSON格式的字符串转换回Go语言中的结构体或其他数据类型。

JSON序列化

序列化使用 json.Marshal 函数将Go语言的数据结构转换为JSON格式的字节切片(byte slice)

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`
	Email   string `json:"email"`
	Address string `json:"address,omitempty"` // omitempty 表示如果字段为空(默认值),则不包含在JSON中
}

func main() {
	p := Person{
		Name:    "张三",
		Age:     30,
		Email:   "[email protected]",
		Address: "",
	}

	// 序列化
	data, err := json.Marshal(p)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(data)) // 输出:{"name":"张三","age":30,"email":"[email protected]

在上面的例子中,我们定义了一个 Person 结构体,并使用结构体标签(struct tags)来指定JSON字段的名称。json.Marshal 函数将 Person 实例序列化为JSON格式的字节切片,然后将其转换为字符串并打印出来。

JSON反序列化

反序列化使用 json.Unmarshal 函数将JSON格式的字节切片转换为Go语言的数据结构。

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`
	Email   string `json:"email"`
	Address string `json:"address,omitempty"`
}

func main() {
	jsonData := []byte(`{
		"name": "张三",
		"age": 30,
		"email": "[email protected]"
	}`)

	var p Person

	// 反序列化
	err := json.Unmarshal(jsonData, &p)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", p) // 输出:{Name:张三 Age:30 Email:[email protected] Address:}
 

 

在这个例子中,我们定义了一个包含JSON字符串的 jsonData 变量,并使用 json.Unmarshal 函数将其反序列化为 Person 结构体的实例。反序列化成功后,我们可以打印出结构体的内容

注意事项

  • 在结构体标签中,可以使用 omitempty 选项来忽略空值字段。
  • 结构体中的字段名必须是大写开头的,否则在序列化和反序列化时不会被识别。
  • 如果JSON中的字段名与结构体中的字段名不一致,可以使用结构体标签来映射字段名。
  • 序列化和反序列化过程中可能会遇到多种错误,例如数据类型不匹配、字段不存在等,需要对这些错误进行处理。

JSON的映射

在上面的序列化和反序列化的例子当中,可以看到需要定义一些规则来指定如何将Go的结构体字段映射到JSON的键,以及如何将JSON的键映射回Go的结构体字段。Go在JSON的映射上,一般采用结构体(struct)接口(interface{})来进行映射,接下来会分别介绍

结构体映射

结构体在处理JSON的映射过程中,一般采用结构体标签(Struct Tags,结构体标签是附加在结构体字段后面的字符串,它们提供了在序列化和反序列化过程中如何处理字段的额外信息。结构体标签通常由一个或多个键值对组成,键和值之间用逗号分隔,键值对之间用空格分隔。在处理JSON时,json键用于指定JSON字段的名称和其他选项。

常用结构体标签选项

  • json:“name”: 指定JSON字段的名称。如果省略,则默认使用结构体字段的名称。
  • omitempty: 如果字段的值为空(例如,字符串为空、数字为0、布尔值为false、指针或接口为nil),则在序列化时省略该字段。这个选项通常与json:"name"一起使用,如json:“name,omitempty”
  • -: 表示该字段在序列化和反序列化过程中应该被忽略

示例

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`
	Address string `json:"address,omitempty"`
}

func main() {
	p := Person{
		Name:    "张三",
		Age:     30,
		Address: "",
	}

	// 序列化
	data, err := json.Marshal(p)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(data)) // 输出:{"name":"张三","age":30}

	// 反序列化
	jsonData := []byte(`{"name":"李四","age":25,"address":"北京"}`)
	var p2 Person
	err = json.Unmarshal(jsonData, &p2)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", p2) // 输出:{Name:李四 Age:25 Address:北京}

在这个示例中,我们定义了一个Person结构体,其中包含三个字段,每个字段都有自己的结构体标签。在序列化时,由于Address字段的值为空,并且标签中包含了omitempty选项,所以该字段被省略了。在反序列化时,JSON数据中的name、age和address键被映射回结构体中的Name、Age和Address字段

在go1.18及之后的版本,引入了泛型的概念,可以通过泛型来编写更灵活和可重用的代码。创建能够处理任何类型的JSON数据的函数

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

// 泛型函数,用于解析JSON到任意类型的对象
func ParseJSON[T any](data []byte, obj T) (T, error) {
	err := json.Unmarshal(data, &obj)
	if err != nil {
		return obj, err
	}
	return obj, nil
}

type Person struct {
	Name  string `json:"name"`
	Age   int    `json:"age"`
	Email string `json:"email"`
}

func main() {
	// JSON数据
	jsonData := []byte(`{
		"name": "张三",
		"age": 30,
		"email": "[email protected]"
	}`)

	// 使用泛型函数解析JSON
	var person Person
	person, err := ParseJSON[Person](jsonData, person)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", person) // 输出:{Name:张三 Age:30 Email:[email protected]}
}

 

在这个示例中,我们定义了一个泛型函数 ParseJSON,它接受任意类型的对象作为参数,并将JSON数据解析到该对象中。我们使用类型参数 T 来表示任意类型,并通过泛型函数来解析JSON数据到一个 Person 对象中。

接口映射

结构体映射,比较适合固定和已知的JSON结构,我们可以看上面代码中使用的例子

{
	"name":"张三",
	"ag

其对应的结构体

type Person struct {
	Name    string `json:"name"`
	Age     int    `json:"age"`

如果多添加一个address后

{
	"name":"李四", 
	"age": 25,
 	"address":"北京"

上面的结构体显然无法解析,当然也可以添加一个address字段来解析,但这需要建立在完全了解接收的JSON数据的结构才可以,有很多情况我们可能并不能完全确定数据结构,需要更灵活的方式来处理,一般会使用到接口(interface{})

interface{} 类型在Go中是一个空接口,它可以表示任何类型。这意味着你可以使用 interface{} 来解析任何JSON数据,而不需要预先定义一个具体的结构体。这在处理未知JSON结构或动态字段时非常有用。

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

func main() {
	// JSON数据
	jsonData := []byte(`{
		"name": "张三",
		"age": 30,
		"email": "[email protected]"
	}`)

	// 使用map[string]interface{}来解析JSON
	var result map[string]interface{}
	err := json.Unmarshal(jsonData, &result)
	if err != nil {
		log.Fatal(err)
	}

	// 访问解析后的数据
	name, ok := result["name"].(string)
	if ok {
		fmt.Println("Name:", name)
	}

	age, ok := result["age"].(float64)
	if ok {
		fmt.Println("Age:", age)
	}

	email, ok := result["email"].(string)
	if ok {
		fmt.Println("Email:", email)
	}
}

 

说明:可以看到在获取数据时使用了类型转换的句子来断言现在获取的类型name, ok := result["name"].(string),这里ok返回的是bool类型,表示是否存在这个name

在这个示例中,我们使用 map[string]interface{} 来解析JSON数据。这允许我们动态地访问JSON对象中的字段,而不需要预先定义一个结构体。我们通过类型断言来获取字段的特定类型值。

设计RESTful API的原则

RESTful API是一种遵循REST(Representational State Transfer)架构风格的网络应用程序接口。REST是一种设计网络服务的架构风格,它定义了一组用于创建分布式系统的原则和约束。RESTful API通常使用HTTP协议作为底层传输协议,并利用HTTP的方法(如GET、POST、PUT、DELETE等)来对资源进行操作

设计RESTful API的时候,一般遵循一定的原则:

  • 资源导向:RESTful API应该以资源为中心,每个资源都应有唯一的URL标识。资源通常与数据库中的表或实体对应
  • 无状态:API应该是无状态的,这意味着每个请求都应该包含所有必要的信息,服务器不应该存储任何关于客户端状态的信息。
  • 统一接口:API应该有一套统一的接口,使用标准的HTTP方法(如GET、POST、PUT、DELETE等)来进行资源的CRUD(创建、读取、更新、删除)操作。
  • URL命名规范:URL应该清晰地反映资源结构,使用名词而不是动词,并且应该使用复数形式。
  • 状态码使用:应该使用适当的HTTP状态码来表示请求的结果,例如200表示成功,201表示创建成功,404表示未找到资源等。
  • 数据格式:API应该返回结构化的数据格式,通常是JSON或XML。JSON由于其在Web中的普遍使用和轻量级特性,通常是首选。
  • 过滤、排序和分页:提供参数来允许客户端对返回的数据进行过滤、排序和分页,以便于用户根据需要获取数据。
  • 错误处理:应该提供有用的错误信息,以便于开发者调试和理解问题。错误信息应该清晰、具体,并且不应该暴露后端实现的细节。
  • 安全性:API应该考虑到安全性,使用HTTPS来保护数据传输,使用适当的认证和授权机制来保护资源。
  • 限流和缓存:为了防止滥用和提高性能,应该实现限流和缓存机制。
  • 版本控制:随着API的发展,可能需要更改现有的API。为了不破坏现有的客户端,应该实现版本控制,以便于平滑过渡。
  • 文档:提供详尽的文档是至关重要的,它应该包括API的每个端点的详细描述、参数、请求和响应示例等。
  • 可测试性:API应该易于测试,可以通过单元测试、集成测试和端到端测试来确保其可靠性和稳定性。
  • 可扩展性:设计时应该考虑到未来的扩展,API应该能够适应更多的资源和功能而无需大规模重构

实现和使用API

RESTful API实现

下面的简单例子我们单纯使用net/http来实现一个简单的RESTful API,包含了GET,PUT,POST,DELETE四种请求方式。如果使用现有的框架如Gin、Beego、Echo等(这些框架后面会再介绍),会更加方便,灵活,但框架也是基于net/http实现的

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
)

// 定义一个简单的产品结构体
type Product struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Price int    `json:"price"`
}

// 创建一个产品切片,模拟数据库中的数据
var products = []Product{
	{ID: 1, Name: "苹果", Price: 50},
	{ID: 2, Name: "香蕉", Price: 30},
	{ID: 3, Name: "橙子", Price: 40},
}

// 处理POST请求,创建新产品
func createProduct(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	var product Product
	_ = json.NewDecoder(r.Body).Decode(&product)

	products = append(products, product)
	json.NewEncoder(w).Encode(product)
}

// 处理GET请求,获取所有产品
func getProducts(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	json.NewEncoder(w).Encode(products)
}

// 处理GET请求,获取单个产品
func getProduct(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	id := r.URL.Query().Get("id")
	for _, product := range products {
		if fmt.Sprintf("%d", product.ID) == id {
			json.NewEncoder(w).Encode(product)
			return
		}
	}
	http.Error(w, "Product not found", http.StatusNotFound)
}

// 处理PUT请求,更新产品
func updateProduct(w http.ResponseWriter, r *http.Request) {
	if r.Method != "PUT" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	id, err := strconv.Atoi(r.URL.Query().Get("id"))
	if err != nil {
		http.Error(w, "Invalid product ID", http.StatusBadRequest)
		return
	}

	var product Product
	_ = json.NewDecoder(r.Body).Decode(&product)
	product.ID = id

	for i, p := range products {
		if p.ID == id {
			products[i] = product
			json.NewEncoder(w).Encode(product)
			return
		}
	}
	http.Error(w, "Product not found", http.StatusNotFound)
}

// 处理DELETE请求,删除产品
func deleteProduct(w http.ResponseWriter, r *http.Request) {
	if r.Method != "DELETE" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	id, err := strconv.Atoi(r.URL.Query().Get("id"))
	if err != nil {
		http.Error(w, "Invalid product ID", http.StatusBadRequest)
		return
	}

	for i, p := range products {
		if p.ID == id {
			products = append(products[:i], products[i+1:]...)
			break
		}
	}
	json.NewEncoder(w).Encode(map[string]string{"message": "Product deleted"})
}

func main() {
	http.HandleFunc("/products", getProducts)
	http.HandleFunc("/product", getProduct)
	http.HandleFunc("/createProduct", createProduct)
	http.HandleFunc("/updateProduct", updateProduct)
	http.HandleFunc("/deleteProduct", deleteProduct)

	log.Println("Server started on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(

在这个例子中,定义了5个路由以及函数

  • /product:getProducts函数处理GET请求,用于获取所有产品
  • /products:getProduct函数处理GET请求,用于获取单个产品
  • /createProduct:createProduct函数处理POST请求,用于创建新产品
  • /updateProduct:updateProduct函数处理PUT请求,用于更新产品信息
  • /deleteProduct:deleteProduct函数处理DELETE请求,用于删除产品

请求示例及结果

调用RESTful API

调用RESTful API依然是使用net/http,我们在前面的章节已经有过简单介绍。在下面的例子中,我们将使用一个名为https://jsonplaceholder.typicode.com/的公开API,该API提供了一系列模拟的RESTful API端点。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// 定义一个请求结构体
type Request struct {
	Method  string `json:"method"`
	Url     string `json:"url"`
	Headers map[string]string `json:"headers"`
	Body    string `json:"body"`
}

// 处理GET请求
func handleGet(w http.ResponseWriter, r *http.Request) {
	// 解析请求参数
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	id := r.URL.Query().Get("id")

	// 构建请求
	request := Request{
		Method: "GET",
		Url:    fmt.Sprintf("https://jsonplaceholder.typicode.com/todos/%s", id),
		Headers: map[string]string{
			"Content-Type": "application/json",
		},
	}

	// 发起请求
	response, err := http.NewRequest("GET", request.Url, nil)
	if err != nil {
		log.Fatal(err)
	}

	for key, value := range request.Headers {
		response.Header().Set(key, value)
	}

	client := &http.Client{}
	resp, err := client.Do(response)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	// 读取响应体
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	// 输出响应体
	fmt.Fprintf(w, "%s", body)
}

func main() {
	http.HandleFunc("/get", handleGet)

	log.Println("Server started on http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

 

在这个示例中,我们定义了一个Request结构体,用于存储请求的方法、URL、头信息和正文。我们创建了一个handleGet函数,它解析请求参数,构建请求,并发起GET请求。然后,我们使用ioutil.ReadAll函数读取响应体,并将其输出到客户端。

整个例子相当于创建了一个api转发,可以尝试在里面添加一些中间件将获取到的数据存储,清洗再进行优化展示。

标签:web,http,string,err,JSON,json,go,序列化
From: https://www.cnblogs.com/cheyunhua/p/18449342

相关文章

  • c goto动态label跳转
    pg表达式引擎里面各个表达式的串联是使用goto动态label实现的。//定义部分#ifdefined(EEO_USE_COMPUTED_GOTO)staticconstvoid*constdispatch_table[]={&&CASE_EEOP_DONE,&&CASE_EEOP_INNER_FETCHSOME,&&CASE_EEOP_OUTER_FETCHSOME,......
  • 使用宝塔WebHook自动同步Gitlab提交的代码
    一、配置SSH创建SSH密钥打开终端。生成SSH密钥:使用以下命令生成一个新的SSH密钥对:ssh-keygen-trsa-b4096-C"[email protected]"-trsa 指定密钥类型为RSA。-b4096 指定密钥长度为4096位。-C"[email protected]" 用于添加注释(通常是......
  • ​解密 Go runtime.SetFinalizer 的使用
    解密Goruntime.SetFinalizer的使用原创 GoOfficialBlog GoOfficialBlog  2024年10月05日18:45 中国香港 听全文如果我们想在对象GC之前释放一些资源,可以使用returns.SetFinalizer。这就像在函数返回前执行 defer 来释放资源一样。例如:1:使用runtime.......
  • SEHS4517 Web Application Development
    SEHS4517WebApplicationDevelopmentandManagementSemester1,2024-2025AssignmentIndividualAssignment(30%oftheassessmentofthiscourse)Thisindividualassignmentaims:Toconductresearchonrelevantreferencematerialsorsources.Todeve......
  • 好用的websocket 心跳重连js脚本
    varwsUrl='ws://'+(document.domain||'127.0.0.1')+':8282';varws=null;//WebSocket对象varheartbeatTimer=null;//心跳定时器varisReconnect=true;//是否自动重连//创建WebSocket连接//@authhttps://so.csdn.net/so/aifu......
  • CTFWeb篇01
    该篇简单介绍一下打CTFWeb需要使用的一些工具:Dirsearch——一款非常好用的目录扫描工具,基本上所有的常见目录它都可以扫出来,避免了你艰难地测试一个个目录所花费的大量时间。Kali虚拟机——建议优先搭一个linux系统的虚拟机,上面自动搭载了许多工具,同时很多工具在linux系统上可以......
  • web知识点
    题注:当开发人员在线上环境中使用vim编辑器,在使用过程中会留下vim编辑器缓存,当vim异常退出时,缓存会一直留在服务器上,引起网站源码泄露。已知:当vim异常退出时,以index.php为例,第一次产生.index.php.swp第二次产生.index.php.swo第三次产生.index.php.swn查看IP等:控制人太台:ipc......
  • yt downloader website
     isanonlinewebsitethatoffersaconvenienttoolfordownloadingcontentfromYouTube. Whatisit?Itisknownasytdownloader,whichisafreeonlineYouTubedownloadtool.ItprovidesuserswithaneasywaytoobtainvarioustypesofmediafromYo......
  • comp10002 Foundations of Algorithms
    comp10002 Foundations of AlgorithmsSemester 2,2024Assignment 2LearningOutcomesInthisproject,youwilldemonstrateyour understanding ofdynamic memory and linked data structures (Chap- ter 10) and extend your program design, testi......
  • Golang安全开发第一节
    Golang安全开发一、安装Go&编译器基础使用1.安装包地址https://golang.google.cn2.添加环境变量windows直接点击msi安装即可Linuxtar-zxvfxxx.xxx.xxx.tar.gzmv-rgo/use/local/govim/etc/profileexportPATH=$PATH:/usr/local/go/binsource/etc/profile3.......