
本文介绍了如何使用 Go 语言处理从标准输入读取的 JSON 数据流,该数据流中 JSON 结构体之间穿插着非 JSON 字符串(例如 "end")。我们将探讨如何读取数据流,过滤掉非 JSON 内容,并将有效的 JSON 数据反序列化为 Go 结构体。
处理混合 JSON 和非 JSON 内容的数据流
在某些情况下,我们可能需要处理包含 JSON 数据和其他类型数据的混合数据流。例如,一个应用程序可能通过标准输入发送 JSON 结构体,并在每个结构体之后添加一个 "end" 字符串作为分隔符。Go 的 encoding/json 包默认情况下无法直接处理这种数据流,因为它期望输入是纯粹的 JSON 格式。
以下是一种处理这种情况的方法,它放弃了 json.Decoder,转而使用 io.Reader 和 json.Unmarshal:
示例代码
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
)
// MyStruct 定义了要反序列化的 JSON 结构
type MyStruct struct {
Command string `json:"command"`
ID string `json:"id"`
Msg string `json:"msg,omitempty"` //omitempty 表示如果 Msg 字段为空,则在 JSON 中省略
}
func main() {
// 创建一个缓冲区来保存流数据
data := make([]byte, 5000)
// 从 stdin 循环读取数据
for {
n, err := os.Stdin.Read(data)
if err != nil {
fmt.Println("Error reading from stdin:", err)
return // 或者根据需要进行错误处理
}
// 查找换行符的索引,用于分隔 JSON 结构
index := bytes.Index(data[:n], []byte("\n"))
// 如果没有找到换行符,则继续读取更多数据
if index == -1 {
fmt.Println("No newline found, reading more data")
continue
}
// 提取 JSON 数据部分
jsonData := data[:index]
// 创建 MyStruct 实例
var myStruct MyStruct
// 将 JSON 数据反序列化到 MyStruct
err = json.Unmarshal(jsonData, &myStruct)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
continue // 或者根据需要进行错误处理
}
// 对 myStruct 进行操作
fmt.Printf("Received: %+v\n", myStruct)
// 移除已处理的数据和 "end\n" 字符串
remainingData := data[index+len("\nend\n"):]
copy(data, remainingData)
// 重置 buffer 的剩余部分
for i := len(remainingData); i < len(data); i++ {
data[i] = 0
}
}
}代码解释:
- 定义结构体: MyStruct 定义了要从 JSON 数据反序列化到的 Go 结构体。 json:"command" 等标记用于指定 JSON 字段与结构体字段之间的映射关系。
- 读取数据: 使用 os.Stdin.Read(data) 从标准输入读取数据到缓冲区 data 中。
- 查找分隔符: bytes.Index(data[:n], []byte("\n")) 查找换行符的索引,用于分隔 JSON 结构体和 end 字符串。 data[:n] 确保只在实际读取到的数据范围内查找。
- 提取 JSON 数据: jsonData := data[:index] 提取 JSON 数据部分。
- 反序列化 JSON: json.Unmarshal(jsonData, &myStruct) 将 JSON 数据反序列化到 MyStruct 结构体中。
- 错误处理: 代码包含错误处理逻辑,用于处理读取和反序列化过程中可能出现的错误。
- 移除已处理的数据: remainingData := data[index+len("\nend\n"):] 获取剩余未处理的数据,并将数据复制到 data 缓冲区的起始位置。 copy(data, remainingData) 确保数据不会丢失。
- 重置缓冲区: 将缓冲区中剩余部分重置为 0,避免旧数据干扰下一次读取。
编译并运行代码
将代码保存为 main.go,然后在终端中运行以下命令:
go run main.go
现在,你可以向标准输入发送包含 JSON 数据和 "end" 字符串的混合数据流。例如:
企业易站网站内容发布管理系统,根据国际最流行的B/S模式设计,同时采用了页面模板与域变量相结合的网站发布管理方式,企业能够很方便的使用这套软件轻松迅速地发布网站以及页面出来,包含了"系统管理"、"权限管理"、"网站发布"、"新闻发布"、"信息自动采集"、"流量统计"、"
echo '{"command": "ack", "id": "1231231"}' | cat - && echo 'end' | cat - && echo '{"command": "fail", "id": "1231231"}' | cat - && echo 'end' | cat -或者,你可以将数据存储在文件中,然后使用以下命令将文件内容重定向到程序的标准输入:
cat input.txt | go run main.go
其中 input.txt 包含以下内容:
{"command": "ack", "id": "1231231"}
end
{"command": "fail", "id": "1231231"}
end
{
"command": "log",
"msg": "hello world!"
}
end程序将解析 JSON 数据并将其打印到控制台。
注意事项
- 缓冲区大小: data := make([]byte, 5000) 定义了缓冲区的大小。你需要根据实际情况调整缓冲区的大小,以确保能够容纳完整的 JSON 结构体和分隔符。
- 错误处理: 示例代码包含基本的错误处理,但在实际应用中,你可能需要更完善的错误处理机制,例如记录错误日志或采取其他补救措施。
- 性能: 对于非常大的数据流,这种方法可能不是最有效的。在这种情况下,可以考虑使用流式 JSON 解析器,例如 github.com/json-iterator/go,它可以更有效地处理大型 JSON 数据。
- 分隔符的灵活性: 代码假设分隔符始终是 "end\n"。如果分隔符可能发生变化,你需要修改代码以适应不同的分隔符。
- 多余的 end 字符: 如果JSON数据中存在 end 字符,可能会导致解析错误。
总结
本文介绍了一种使用 Go 语言处理包含非 JSON 内容的 JSON 数据流的方法。通过使用 io.Reader 和 json.Unmarshal,我们可以灵活地读取数据流,过滤掉非 JSON 内容,并将有效的 JSON 数据反序列化为 Go 结构体。在实际应用中,你需要根据具体情况调整代码,例如缓冲区大小、错误处理和性能优化。









