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

如何在JSON文件中修改数组?Rust下交易记录追加优化问询

问题:如何在Rust中向JSON文件的数组追加记录而不全量覆盖?

背景与现有实现

我正在通过编写简易记账程序学习Rust,选择本地JSON文件存储数据,其结构如下:

{
    "wallet": "wallet1",
    "balance": 2000.0,
    "transactions": [
        {
            "tr_type": "Withdraw",
            "date": 20230522,
            "volume": 300.0
        },
        {
            "tr_type": "Deposit",
            "date": 20230422,
            "volume": 150.0
        },
        {
            "tr_type": "Withdraw",
            "date": 20230222,
            "volume": 300.0
        },
        {
            "tr_type": "Deposit",
            "date": 20230707,
            "volume": 200.0
        }
    ]
}

程序需要读取文件数据进行计算展示,并接收用户新增交易记录写入JSON文件。当前实现代码如下:

use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::prelude::*;

#[derive(Serialize, Deserialize, Debug)]
struct Transaction {
    tr_type: TRtype,
    date: u64,
    volume: f32,
}

#[derive(Serialize, Deserialize, Debug)]
struct Account {
    wallet: String,
    balance: f32,
    transactions: Vec<Transaction>,
}

#[derive(Serialize, Deserialize, Debug)]
enum TRtype {
    Deposit,
    Withdraw,
}

fn main() -> std::io::Result<()> {
    let mut myfile = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("wallets.json")?;

    let mut raw_json = String::new();
    myfile.read_to_string(&mut raw_json)?;

    let mut deserialized: Account = serde_json::from_str(&raw_json)?;
    for trx in &deserialized.transactions {
        println!(
            "transaction: {:?}, volume = {}, date = {}",
            trx.tr_type, trx.volume, trx.date
        );
    }

    let new_transaction: Transaction = Transaction {
        tr_type: TRtype::Deposit,
        date: 20230707,
        volume: 1500.0,
    };

    deserialized.transactions.push(new_transaction);

    let serialized = serde_json::to_string(&deserialized).unwrap();

    myfile.rewind().unwrap();
    myfile.write_all(serialized.as_bytes())?;

    Ok(())
}

当前实现每次新增交易都会全量覆盖整个JSON文件,不够优雅。请问是否存在方法,可直接访问文件中的transactions数组并追加记录,而不改动其他内容?


解决方案

方法1:手动修改文件末尾(轻量但依赖JSON格式)

要直接追加数组元素,核心是修改JSON文件的末尾部分:原文件以]}结尾,我们需要将其替换为, 新交易对象]}。这种方法不需要全量解析和序列化,但依赖JSON文件的格式化规则(比如末尾无多余空格、数组闭合后直接跟对象闭合)。

实现步骤:

  1. 打开文件并获取文件大小
  2. 从文件末尾向前读取,找到]}的位置
  3. 截断文件到该位置(去掉原有的]})
  4. 根据数组是否为空,写入逗号(如果已有元素)和新序列化的交易对象
  5. 写入]}闭合JSON

对应的Rust代码示例:

use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};

#[derive(Serialize, Deserialize, Debug)]
struct Transaction {
    tr_type: TRtype,
    date: u64,
    volume: f32,
}

#[derive(Serialize, Deserialize, Debug)]
enum TRtype {
    Deposit,
    Withdraw,
}

fn append_transaction(new_trx: &Transaction) -> std::io::Result<()> {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("wallets.json")?;

    let file_size = file.metadata()?.len();

    if file_size == 0 {
        // 文件为空,初始化完整的Account结构
        let account = serde_json::json!({
            "wallet": "wallet1",
            "balance": 2000.0,
            "transactions": [new_trx]
        });
        let serialized = serde_json::to_string_pretty(&account)?;
        file.write_all(serialized.as_bytes())?;
        return Ok(());
    }

    // 从末尾向前查找"]}"的位置
    let mut buffer = [0; 2];
    let mut pos = file_size;
    let mut found = false;
    while pos >= 2 {
        pos -= 2;
        file.seek(SeekFrom::Start(pos))?;
        file.read_exact(&mut buffer)?;
        if buffer == b"]}" {
            found = true;
            break;
        }
        // 回退1字节,处理可能的空格或换行(这里简化处理)
        pos += 1;
    }

    if !found {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "Invalid JSON format: cannot find transactions array end",
        ));
    }

    // 截断文件到"]}"的起始位置
    file.set_len(pos)?;
    file.seek(SeekFrom::Start(pos))?;

    // 检查数组是否为空
    file.seek(SeekFrom::Start(pos - 1))?;
    let mut char_buf = [0; 1];
    file.read_exact(&mut char_buf)?;
    if char_buf != b"[" {
        // 已有元素,先写逗号
        file.write_all(b", ")?;
    }

    // 写入新交易
    let serialized_trx = serde_json::to_string(new_trx)?;
    file.write_all(serialized_trx.as_bytes())?;
    // 闭合数组和对象
    file.write_all(b"]}")?;

    Ok(())
}

fn main() -> std::io::Result<()> {
    let new_transaction = Transaction {
        tr_type: TRtype::Deposit,
        date: 20230708,
        volume: 1500.0,
    };

    append_transaction(&new_transaction)?;
    println!("Transaction appended successfully");

    Ok(())
}

注意事项:

  • 该代码假设JSON格式规范,若文件是美化后的格式(比如数组闭合前有换行/空格),需要额外处理格式字符。
  • 存在并发修改风险,若有其他进程同时操作文件,可能导致文件损坏,建议配合文件锁使用。
  • 如果需要同步更新balance字段,这种方法无法关联处理,此时全量覆盖反而更可靠。

方法2:使用流式JSON处理库(更健壮)

如果需要更健壮的解决方案,可以使用支持流式修改JSON的库,比如通过serde_json的低级流式API定位到transactions数组后直接追加元素,或者使用json-patch类库实现增量修改。

不过这种方式实现复杂度较高,对于小型记账程序而言,全量覆盖是更简单可靠的选择——除非JSON文件体积达到GB级,否则全量序列化和写入的性能影响可以忽略。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 22:24:52