Go:如何将含io.Reader的结构体流式序列化为带转义的JSON
流式序列化含io.Reader的Go结构体为JSON(自动转义)
问题场景
现有如下Go结构体:
type MyStruct struct { Foo int `json:"foo"` BigString io.Reader `json:"bigString"` // 需要编码为JSON字符串 }
要求将其序列化为JSON时:
- 无需将
BigString的全部内容加载到内存(数据源为文件句柄) - 自动完成JSON转义(如
"转\"等特殊字符处理)
解决方案:自定义io.WriterTo实现流式转义
Go的json包支持识别实现了io.WriterTo接口的类型,编码时会直接调用其WriteTo方法流式写入输出,完美契合内存友好的需求。我们可以封装一个带JSON转义的Reader类型,实现该接口:
1. 定义带转义的Reader包装类型
import ( "bufio" "fmt" "io" ) type JSONEscapedReader struct { r io.Reader }
2. 实现io.WriterTo接口
在WriteTo方法中,逐块读取源数据、完成JSON转义后写入输出流:
func (jr JSONEscapedReader) WriteTo(w io.Writer) (int64, error) { // 写入JSON字符串起始引号 n, err := w.Write([]byte(`"`)) if err != nil { return int64(n), err } total := int64(n) // 用bufio做缓冲读取,块大小可根据需求调整(这里用4KB) br := bufio.NewReader(jr.r) buf := make([]byte, 4096) for { readN, err := br.Read(buf) if readN > 0 { // 转义当前块并写入 escapedN, err := escapeJSONBytes(w, buf[:readN]) if err != nil { return total + int64(escapedN), err } total += int64(escapedN) } if err == io.EOF { break } if err != nil { return total, err } } // 写入JSON字符串结束引号 n, err = w.Write([]byte(`"`)) total += int64(n) return total, err } // escapeJSONBytes 对字节切片执行JSON标准转义并写入目标Writer func escapeJSONBytes(w io.Writer, b []byte) (int, error) { var total int for _, c := range b { var escaped []byte switch c { case '"': escaped = []byte(`\"`) case '\\': escaped = []byte(`\\`) case '\b': escaped = []byte(`\b`) case '\f': escaped = []byte(`\f`) case '\n': escaped = []byte(`\n`) case '\r': escaped = []byte(`\r`) case '\t': escaped = []byte(`\t`) default: // 控制字符转义为\uXXXX格式 if c < 0x20 { escaped = []byte(fmt.Sprintf(`\u%04x`, c)) } else { // 普通字符直接写入 n, err := w.Write([]byte{c}) total += n if err != nil { return total, err } continue } } n, err := w.Write(escaped) total += n if err != nil { return total, err } } return total, nil }
3. 修改结构体并使用
将原结构体的BigString字段类型替换为JSONEscapedReader,然后把文件句柄包装后传入:
import ( "encoding/json" "log" "os" ) type MyStruct struct { Foo int `json:"foo"` BigString JSONEscapedReader `json:"bigString"` } func main() { // 打开目标文件(数据源) file, err := os.Open("large-content.txt") if err != nil { log.Fatal(err) } defer file.Close() // 初始化结构体 data := MyStruct{ Foo: 42, BigString: JSONEscapedReader{r: file}, } // 流式编码到输出(这里用标准输出,可替换为文件、HTTP响应等) encoder := json.NewEncoder(os.Stdout) if err := encoder.Encode(data); err != nil { log.Fatal(err) } }
方案优势
- 内存友好:仅以固定大小的块读取数据,不会加载整个文件到内存,适合处理GB级大文件
- 标准转义:完全遵循JSON规范处理特殊字符,避免手动转义遗漏
- 流式兼容:直接适配
json.Encoder的流式逻辑,可无缝对接各种io.Writer(文件、网络连接等)
内容的提问来源于stack exchange,提问作者pamphlet
相关产品推荐
相关产品推荐

