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

在Rust中无需指定列类型,如何读取扁平化JSON为Polars DataFrame?

Rust中读取扁平化JSON到Polars DataFrame的问题

问题背景

给定如下扁平化JSON示例:

{
  "data": [
    {
      "requestId": "IBM",
      "date": "2024-03-19",
      "sales": 61860,
      "company": "International Business Machines",
      "price": 193.34,
      "score": 7
    },
    {
      "requestId": "AAPL",
      "date": "2024-03-19",
      "sales": 383285,
      "company": "Apple Inc.",
      "price": 176.08,
      "score": 9
    },
    {
      "requestId": "MSFT",
      "date": "2024-03-19",
      "sales": 211915,
      "company": "Microsoft Corporation",
      "price": 421.41,
      "score": 7
    } 
  ]
}

数据仅包含整数、浮点数和字符串,存在两个问题:

  1. 如何无需为每个列标注数据类型,将该结构读取为Polars DataFrame?
  2. 当存在200+动态变化的列时,使用如下结构体中的HashMap<String, serde_json::Value>来动态存储列是否合适?
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct Row {
    requestId: String,
    date: String,
    #[serde(flatten)]
    company_data: HashMap<String, serde_json::Value>,
}

解决方案

1. 无需指定列类型读取JSON为Polars DataFrame

方法一:使用Polars内置JSON读取功能

Polars支持直接读取JSON并自动推断列类型,无需手动指定。可通过JsonReader读取文件或内存中的JSON字符串:

use polars::prelude::*;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 读取本地JSON文件
    let file = File::open("data.json")?;
    let df = JsonReader::new(file)
        .with_infer_schema_length(Some(100)) // 指定用于推断类型的样本行数,默认100
        .finish()?
        .explode(["data"])? // 展开外层的data数组
        .unnest(["data"])?; // 将嵌套结构体拆分为独立列
    
    println!("{}", df);
    Ok(())
}

Polars会自动根据样本数据识别整数、浮点数、字符串等类型,全程无需手动定义列类型。

方法二:Serde解析后转换为DataFrame

若需要预处理步骤,可先用Serde将JSON解析为动态结构,再转换为Polars DataFrame:

use serde_json::Value;
use polars::prelude::*;
use std::fs::read_to_string;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let json_str = read_to_string("data.json")?;
    let json_value: Value = serde_json::from_str(&json_str)?;
    
    // 提取data数组
    let data = json_value.get("data").unwrap().as_array().unwrap();
    
    // 收集所有唯一列名
    let keys: Vec<&str> = data.iter()
        .flat_map(|obj| obj.as_object().unwrap().keys().map(|k| k.as_str()))
        .collect::<std::collections::HashSet<_>>()
        .into_iter()
        .collect();
    
    let mut columns = Vec::new();
    for key in keys {
        // 提取列值并转换为对应类型的Series
        let values: Vec<Value> = data.iter()
            .map(|obj| obj.get(key).unwrap().clone())
            .collect();
        
        let series = match &values[0] {
            Value::String(_) => Series::new(key, values.into_iter().map(|v| v.as_str().unwrap().to_string()).collect()),
            Value::Number(n) if n.is_i64() => Series::new(key, values.into_iter().map(|v| v.as_i64().unwrap()).collect()),
            Value::Number(n) if n.is_f64() => Series::new(key, values.into_iter().map(|v| v.as_f64().unwrap()).collect()),
            _ => panic!("不支持的数据类型"),
        };
        columns.push(series);
    }
    
    let df = DataFrame::new(columns)?;
    println!("{}", df);
    Ok(())
}

这种方式适合需要自定义预处理逻辑的场景,但手动类型判断会增加代码复杂度。

2. 200+动态列使用HashMap的适用性分析

可行场景

  • 当列名、数量完全不可预测,无法提前定义结构体字段时,HashMap<String, serde_json::Value>可以灵活容纳任意键值对,作为中间解析步骤是可行的。

核心缺点

  • 性能损耗大:HashMap的哈希查找、serde_json::Value的动态类型判断都会带来额外开销,200+列的情况下,每一行遍历HashMap会显著降低处理效率。
  • 类型不安全:动态类型需要手动判断,容易出现运行时错误,无法利用Rust编译期类型检查的优势。
  • 转换繁琐:需要手动将HashMap中的值转换为Polars Series,重复代码多且易出错。

更优替代方案

  • 优先用Polars内置功能:直接通过JsonReader自动推断Schema,Polars会高效处理动态列,自动优化类型,无需手动维护HashMap。
  • 用serde_json::Map替代HashMap:serde_json::Map是Serde内置的有序键值结构,更适配JSON对象解析,能保持列顺序,转换为DataFrame更方便。
  • 动态结构体生成:若列名半固定,可通过宏结合Serde动态生成结构体,但实现复杂,仅适合特定场景。

综上,200+动态列场景下,优先选择Polars内置的JSON读取功能;若必须用Serde解析,建议用serde_json::Map替代HashMap以减少性能损耗。


内容的提问来源于Stack Exchange,提问作者Trevor Seibert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:50:03