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

Serde JSON反序列化`{c:[{}]}`为RawValue为何产生堆分配?

为什么会产生意料之外的堆分配

这一分配来自serde_json的嵌套结构跟踪逻辑,是框架为了避免递归解析JSON触发栈溢出做的设计:

  • 反序列化&RawValue类型时,必须先扫描对应JSON片段的起止边界,这个过程需要匹配[]、{}的嵌套关系来确定结束位置
  • serde_json没有使用函数递归处理嵌套,而是用Vec作为迭代扫描的状态栈,Vec默认初始化时不会分配内存,仅在第一次插入元素时触发堆分配
  • 当RawValue内容为[2, 3]时,数组内部都是基本类型,扫描过程中没有遇到新的嵌套复合结构,不需要向状态栈插入元素,因此不会产生分配
  • 当RawValue内容为[{}]时,数组内部嵌套了对象,扫描到{时需要向状态栈插入标记记录嵌套层级,这就触发了Vec的第一次堆分配
零堆分配调整方案

有两种可落地的方案:

方案1:启用serde_json的smallvec特性(无侵入,推荐)

serde_json官方提供了smallvec特性支持,开启后嵌套状态栈会改用SmallVec实现,默认在栈上预留了128层嵌套的存储空间,只要你的JSON嵌套深度不超过128,全程不会触发堆分配。
仅需修改Cargo.toml中的serde_json依赖声明即可,不需要修改任何业务代码:

serde_json = { version = "1.0", features = ["raw_value", "smallvec"] }

方案2:手动解析边界(极致性能场景)

如果你确定输入JSON结构固定,可以手动扫描外层结构切出c字段对应的切片,再转换为&RawValue,完全绕开serde_json的嵌套扫描逻辑,天然零分配:

#[derive(Deserialize, Debug)]
struct MyStruct<'a> {
    #[serde(borrow)]
    c: &'a serde_json::value::RawValue,
}

fn main() {
    let msg = r#"{"c":[{}]}"#;
    // 示例逻辑:手动定位c字段值的起止位置,生产环境使用需要补充通用扫描和边界校验
    let c_slice = &msg[5..msg.len() - 1];
    let c = serde_json::from_str::<&serde_json::value::RawValue>(c_slice).unwrap();
    let my_struct = MyStruct { c };
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 18:27:02