You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go:如何将含io.Reader的结构体流式序列化为带转义的JSON

流式序列化含io.Reader的Go结构体为JSON(自动转义)

问题场景

现有如下Go结构体:

type MyStruct struct {
  Foo int `json:"foo"`
  BigString io.Reader `json:"bigString"` // 需要编码为JSON字符串
}

要求将其序列化为JSON时:

  1. 无需将BigString的全部内容加载到内存(数据源为文件句柄)
  2. 自动完成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:47:07