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

serde_json下实现JSON数组惰性反序列化的更优方法

问题描述

使用serde_json将超长对象数组反序列化为Vec<T>时,必须预先将整个数组加载到内存,处理耗时较长。希望改为迭代读取数组内的元素,以此避免预加载带来的额外处理开销与内存占用。

目前的实现思路

StreamDeserializer仅能迭代连续排列的自定界JSON类型,无法直接处理带外层方括号、元素分隔逗号的标准JSON数组,因此自行实现了一个自定义结构体,为其实现Read特性,在封装原有Read读取流的基础上,自动剥离JSON外层数组的首尾方括号,以及数组元素间的所有逗号。

举例来说,该读取器会将输入的标准JSON [{"name": "foo"}, {"name": "bar"}, {"name": "baz"}] 转换为连续的JSON片段序列 {"name": "foo"} {"name": "bar"} {"name": "baz"},使其满足StreamDeserializer的输入要求。

以下是完整的实现代码:

use std::io;

/// 实现 `Read` 特性,将外层为数组的JSON输入转换为相邻的连续JSON值序列
/// 配合 `serde_json::StreamDeserializer` 完成解析
pub(crate) struct ArrayStreamReader<T> {
    inner: T,
    depth: Option<usize>,
    inside_string: bool,
    escape_next: bool,
}

impl<T: io::Read> ArrayStreamReader<T> {
    pub(crate) fn new_buffered(inner: T) -> io::BufReader<Self> {
        io::BufReader::new(ArrayStreamReader {
            inner,
            depth: None,
            inside_string: false,
            escape_next: false,
        })
    }
}

#[inline]
fn do_copy(dst: &mut [u8], src: &[u8], len: usize) {
    if len == 1 {
        dst[0] = src[0]; // 避免单字节场景的memcpy开销
    } else {
        dst[..len].copy_from_slice(&src[..len]);
    }
}

impl<T: io::Read> io::Read for ArrayStreamReader<T> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        if buf.is_empty() {
            return Ok(0);
        }

        let mut tmp = vec![0u8; buf.len()];

        // 当读取缓冲区很小(比如长度为1)时,可能所有字节都被过滤
        // 此时循环重试,直到从内层流读到可写入缓冲区的有效字节,或者读到EOF
        loop {
            let byte_count = self.inner.read(&mut tmp)?;
            if byte_count == 0 {
                return if self.depth.is_some() {
                    Err(io::ErrorKind::UnexpectedEof.into())
                } else {
                    Ok(0)
                };
            }

            let mut tmp_pos = 0;
            let mut buf_pos = 0;
            for (i, b) in tmp.iter().cloned().enumerate() {
                if self.depth.is_none() {
                    match b {
                        b'[' => {
                            tmp_pos = i + 1;
                            self.depth = Some(0);
                        },
                        b if b.is_ascii_whitespace() => {},
                        b'\0' => break,
                        _ => return Err(io::ErrorKind::InvalidData.into()),
                    }
                    continue;
                }

                if self.inside_string {
                    match b {
                        _ if self.escape_next => self.escape_next = false,
                        b'\\' => self.escape_next = true,
                        b'"' if !self.escape_next => self.inside_string = false,
                        _ => {},
                    }
                    continue;
                }

                let depth = self.depth.unwrap();
                match b {
                    b'[' | b'{' => self.depth = Some(depth + 1),
                    b']' | b'}' if depth > 0 => self.depth = Some(depth - 1),
                    b'"' => self.inside_string = true,
                    b'}' if depth == 0 => return Err(io::ErrorKind::InvalidData.into()),
                    b',' | b']' if depth == 0 => {
                        let len = i - tmp_pos;
                        do_copy(&mut buf[buf_pos..], &tmp[tmp_pos..], len);
                        tmp_pos = i + 1;
                        buf_pos += len;

                        // 写入空格作为元素分隔符
                        buf[buf_pos] = b' ';
                        buf_pos += 1;

                        if b == b']' {
                            // 到达外层数组末尾,重置状态支持后续读取其他数组
                            self.depth = None;
                        }
                    },
                    _ => {},
                }
            }

            if tmp_pos < byte_count {
                let len = byte_count - tmp_pos;
                do_copy(&mut buf[buf_pos..], &tmp[tmp_pos..], len);
                buf_pos += len;
            }

            if buf_pos > 0 {
                // 只要写入了有效字节,就返回实际写入长度,否则继续循环重试
                return Ok(buf_pos);
            }
        }
    }
}

该读取器的实际使用方式如下:

use std::io;
use serde::Deserialize;

#[derive(Deserialize)]
struct Item {
    name: String,
}

fn main() -> io::Result<()> {
    let json = br#"[{"name": "foo"}, {"name": "bar"}]"#;
    let wrapped = ArrayStreamReader::new_buffered(&json[..]);
    let first_item: Item = serde_json::Deserializer::from_reader(wrapped)
        .into_iter()
        .next()
        .unwrap()?;
    assert_eq!(first_item.name, "foo");
    Ok(())
}

最终提问

上述自定义实现的开发成本较高,想必存在更成熟、更简便的实现方案,请问应当如何实现基于serde_json的JSON数组惰性反序列化?


方案解答

完全不需要自行实现自定义Read封装,serde_json原生就支持JSON数组的惰性流式反序列化,全程不需要提前加载全量数组内容。

核心原理

基于Read的serde_json反序列化器本身就是流驱动的:词法分析只会按需读取当前解析位置需要的字节,不会一次性把整个输入流加载到内存。只要不强制将结果反序列化为Vec<T>这类需要全量收集的容器,就可以实现逐元素的惰性读取。

针对外层为数组的JSON结构,serde_json提供了稳定的数组遍历API,会自动识别数组的首尾边界、元素间的分隔逗号,不需要手动修改输入流的内容。

可直接运行的实现代码

use serde::Deserialize;
use serde::Deserializer;
use std::io;

#[derive(Deserialize)]
struct Item {
    name: String,
}

// 单个元素的处理逻辑
fn process_item(item: Item) {
    println!("处理元素: {}", item.name);
}

fn main() -> io::Result<()> {
    // 可以替换为大文件的文件句柄,包一层BufReader即可
    let json = br#"[{"name": "foo"}, {"name": "bar"}, {"name": "baz"}]"#;
    let mut deserializer = serde_json::Deserializer::from_reader(&json[..]);

    // 识别外层数组结构
    let mut seq_access = deserializer.begin_seq()?;
    
    // 惰性逐元素读取,每次仅加载当前元素需要的字节
    loop {
        match seq_access.next_element::<Item>() {
            Ok(item) => process_item(item),
            Err(e) if e.is_eof() => break, // 数组正常读取完毕
            Err(e) => return Err(e.into()),
        }
    }
    Ok(())
}

方案对比优势

  • 代码量从百余行的自定义实现缩减到不到30行,没有额外的维护成本
  • 完全复用serde_json经过多年生产验证的词法分析逻辑,能正确处理字符串内的转义括号、逗号等特殊场景,不会出现自定义实现的解析bug
  • 内存占用仅和单元素大小、内部读取缓冲区大小相关,和整个数组的总长度完全无关,可以处理GB级别的超大JSON数组,不会因为全量加载导致内存溢出
  • 读取大文件时,直接将std::fs::File包一层io::BufReader传入反序列化器即可,不需要提前将整个文件读入内存。

注意事项

  • 不要把输入流提前加载为全量字节数组再传入,否则会丢失惰性读取的优势
  • 迭代过程中如果需要提前终止,直接跳出循环即可,不需要额外的流清理逻辑
  • 该方案完全兼容嵌套数组结构,按照实际元素类型指定泛型参数即可正确解析。

内容的提问来源于stack exchange,提问作者Michael Morgan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 18:36:02