0

0

Go语言中正确发送和接收JSON数据:避免fmt.Fprint陷阱

聖光之護

聖光之護

发布时间:2025-10-11 10:48:43

|

321人浏览过

|

来源于php中文网

原创

Go语言中正确发送和接收JSON数据:避免fmt.Fprint陷阱

本文深入探讨了Go语言HTTP服务中发送JSON数据时一个常见的陷阱:错误地使用fmt.Fprint输出字节切片,导致客户端解码失败。通过分析fmt.Fprint与http.ResponseWriter.Write对[]byte的不同处理机制,文章提供了正确的解决方案,并分享了在构建Go语言API时处理JSON响应的最佳实践,确保数据传输的准确性和效率。

引言:Go语言中处理JSON的常见场景

go语言中构建web服务或api时,json作为一种轻量级的数据交换格式被广泛应用。服务器通常需要将go结构体编码json字符串发送给客户端,而客户端则需要接收并解码这些json数据。虽然go标准库提供了强大的encoding/json包来处理json的编解码,但在实际操作中,尤其是在将编码后的json字节写入http响应时,开发者可能会遇到一些意想不到的问题。

问题重现:Go服务器发送JSON,客户端解码失败

考虑一个典型的场景:Go服务器接收到客户端的请求后,构造一个Go结构体,将其编码为JSON,并通过http.ResponseWriter发送回客户端。客户端接收到响应后,尝试解码该JSON。

以下是原始服务器端的关键代码片段,展示了如何编码JSON并尝试发送:

// Message 结构体定义 (假设在服务器和客户端都存在)
type ClientId int
type Message struct {
    What     int `json:"What"`
    Tag      int `json:"Tag"`
    Id       int `json:"Id"`
    ClientId ClientId `json:"ClientId"`
    X        int `json:"X"`
    Y        int `json:"Y"`
}

// Join 方法处理客户端的连接请求
func (network *Network) Join(
    w http.ResponseWriter,
    r *http.Request) {
    log.Println("client wants to join")
    message := Message{-1, -1, -1, ClientId(len(network.Clients)), -1, -1}
    var buffer bytes.Buffer
    enc := json.NewEncoder(&buffer)

    err := enc.Encode(message)
    if err != nil {
        fmt.Println("error encoding the response to a join request")
        log.Fatal(err)
    }

    fmt.Printf("the json: %s\n", buffer.Bytes()) // 用于调试输出
    fmt.Fprint(w, buffer.Bytes()) // **问题所在**:使用 fmt.Fprint 发送字节切片
}

客户端代码则相对直接,它发送一个GET请求,并尝试解码响应:

func main() {
    var clientId ClientId
    var message Message
    resp, err := http.Get("http://localhost:5000/join")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close() // 确保关闭响应体

    fmt.Println(resp.Status)
    dec := json.NewDecoder(resp.Body)
    err = dec.Decode(&message) // 尝试解码
    if err != nil {
        fmt.Println("error decoding the response to the join request")
        log.Fatal(err) // 客户端在此处崩溃
    }

    fmt.Println(message)
    fmt.Println("with clientId", message.ClientId)
}

运行服务器和客户端后,观察到以下现象:

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

  • 服务器日志显示JSON已正确编码,例如the json: {"What":-1,"Tag":-1,"Id":-1,"ClientId":0,"X":-1,"Y":-1}。
  • 客户端接收到200 OK状态码
  • 客户端在尝试解码时报错:error decoding the response to the join request,具体错误是invalid character "3" after array element。
  • 进一步调试,客户端使用ioutil.ReadAll(resp.Body)读取响应体并打印,发现输出并非预期的JSON字符串,而是字节的十进制表示,例如the json: [123 34 87 104 97 116 ...]。

这个错误信息和响应体的奇怪输出让许多初学者感到困惑,因为编码后的JSON中并没有字符3,而且响应体变成了字节数组的字符串表示。

深入剖析:fmt.Fprint与http.ResponseWriter的误用

问题的核心在于服务器端使用了fmt.Fprint(w, buffer.Bytes())来发送JSON数据。fmt.Fprint是一个格式化输出函数,它的设计目的是将数据以人类可读的格式写入输出流。当fmt.Fprint遇到一个[]byte类型的参数时,它不会将其视为一个原始的字节序列来直接写入,而是会将其中的每一个字节当作一个整数,并以十进制形式打印出来,并在每个数字之间添加空格。这就是为什么客户端会看到[123 34 87 104 97 116 ...]这样的输出。

例如,JSON字符串{"What":...}的第一个字符是{,其ASCII码是123。fmt.Fprint会将其打印为123。紧接着是",ASCII码是34,打印为34。这样,原始的JSON结构就被破坏了,变成了由空格分隔的数字字符串。客户端的json.NewDecoder在尝试解析这个非标准的字符串时,自然会因为遇到非JSON格式的字符(例如数字3在不该出现的位置)而报错。

http.ResponseWriter接口提供了一个Write([]byte) (int, error)方法,这个方法正是用于将原始字节数据直接写入HTTP响应体,而不会进行任何格式化处理。

企奶奶
企奶奶

一款专注于企业信息查询的智能大模型,企奶奶查企业,像聊天一样简单。

下载

解决方案:使用w.Write()发送原始字节

要解决这个问题,服务器端只需将fmt.Fprint(w, buffer.Bytes())替换为w.Write(buffer.Bytes())。w.Write()会直接将buffer.Bytes()中的原始字节序列写入HTTP响应流,确保客户端接收到的是未经修改的、正确的JSON数据。

修正后的服务器端Join方法如下:

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    // 其他导入
)

// Message 结构体定义 (同上)
type ClientId int
type Message struct {
    What     int `json:"What"`
    Tag      int `json:"Tag"`
    Id       int `json:"Id"`
    ClientId ClientId `json:"ClientId"`
    X        int `json:"X"`
    Y        int `json:"Y"`
}

// Join 方法处理客户端的连接请求
func (network *Network) Join(
    w http.ResponseWriter,
    r *http.Request) {
    log.Println("client wants to join")
    message := Message{-1, -1, -1, ClientId(len(network.Clients)), -1, -1}
    var buffer bytes.Buffer
    enc := json.NewEncoder(&buffer)

    err := enc.Encode(message)
    if err != nil {
        fmt.Println("error encoding the response to a join request")
        log.Fatal(err)
    }

    // 最佳实践:设置 Content-Type 头
    w.Header().Set("Content-Type", "application/json")

    // **修正**:使用 w.Write() 发送原始字节
    _, err = w.Write(buffer.Bytes()) 
    if err != nil {
        fmt.Println("error writing response to client")
        log.Fatal(err)
    }
    fmt.Printf("the json: %s\n", buffer.Bytes()) // 调试输出不受影响
}

经过这个修改后,客户端将能够正确接收并解码JSON响应,不再出现invalid character "3"的错误。客户端代码无需任何修改即可正常工作,因为它期望接收的是合法的JSON数据流,而w.Write()正是提供了这样的数据流。

最佳实践与注意事项

  1. 设置 Content-Type 头:在发送JSON响应时,始终应该设置Content-Type头为application/json。这告知客户端响应体的内容类型,有助于客户端正确处理数据。

    w.Header().Set("Content-Type", "application/json")
  2. 更简洁的JSON直接写入方式:如果不需要将JSON编码到bytes.Buffer中进行额外的处理(例如打印到日志),可以直接将json.Encoder绑定到http.ResponseWriter上,这样可以避免中间的bytes.Buffer,代码更简洁高效。

    func (network *Network) Join(
        w http.ResponseWriter,
        r *http.Request) {
        log.Println("client wants to join")
        message := Message{-1, -1, -1, ClientId(len(network.Clients)), -1, -1}
    
        w.Header().Set("Content-Type", "application/json") // 同样需要设置 Content-Type
    
        // 直接编码并写入响应体
        err := json.NewEncoder(w).Encode(message)
        if err != nil {
            fmt.Println("error encoding and writing response to client")
            // 此时可能已经写入部分头信息,需要更优雅的错误处理,例如 http.Error
            http.Error(w, "Internal server error", http.StatusInternalServerError)
            log.Printf("Error encoding/writing JSON: %v", err)
            return
        }
    }
  3. 错误处理的重要性:在网络编程中,对所有可能发生的错误(如JSON编码失败、写入响应失败)进行适当的处理至关重要。例如,使用http.Error向客户端返回错误状态码和消息,而不是简单地log.Fatal,因为log.Fatal会终止整个服务器进程。

总结

在Go语言中处理HTTP响应时,理解fmt.Fprint和http.ResponseWriter.Write对[]byte参数的不同处理方式至关重要。fmt.Fprint用于格式化输出,会将字节切片中的每个字节解释为整数并打印;而w.Write()则是用于直接写入原始字节数据,这正是发送JSON等二进制数据所需要的。通过采用w.Write()并结合设置Content-Type头以及适当的错误处理,我们可以确保Go服务器正确、高效地发送JSON响应,从而构建健壮可靠的API服务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

412

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

533

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

309

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

74

2025.09.10

scripterror怎么解决
scripterror怎么解决

scripterror的解决办法有检查语法、文件路径、检查网络连接、浏览器兼容性、使用try-catch语句、使用开发者工具进行调试、更新浏览器和JavaScript库或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

187

2023.10.18

500error怎么解决
500error怎么解决

500error的解决办法有检查服务器日志、检查代码、检查服务器配置、更新软件版本、重新启动服务、调试代码和寻求帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

280

2023.10.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

258

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.09.04

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

43

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.3万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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