使用 fs 修改 json 文件的单个部分,而不是覆盖整个内容

Posted

技术标签:

【中文标题】使用 fs 修改 json 文件的单个部分,而不是覆盖整个内容【英文标题】:Use fs to modify a single part of a json file, rather than overwriting the entire thing 【发布时间】:2020-08-16 12:05:21 【问题描述】:

对于 Discord 机器人,我目前有一个显示 DnD 会话信息的命令:

.

数据存储在 dndinfo.json 文件中,如下所示:

"time":"**18:00 UK time**",
"date":"**14/05/20**",
"dm":"**Mannion**",
"prime":"Playing",
"smurphy":"Playing",
"calle":"Playing",
"smardon":"Playing",
"james":"Playing",
"mannion":"DMing",
"dex":"Playing",
"module":"Hoard of the Dragon Queen"

我希望用户能够执行命令,例如“!te time 17:00”,这将相应地更新时间。

我目前正在使用此代码:

const Discord = module.require('discord.js');
const fs = require('fs');
const dndinfo = require ('../../dndinfo.json');

module.exports = 
    name: 'test',
    aliases: ['te'],
    category: 'dnd',
    description: 'Updates DnD info',
    usage: '!te',
    run: async (client, message, args) => 

        const time = dndinfo.time;
        let editMessage = message.content.slice(9);

        if (message.content.toLowerCase().includes('time')) 

            fs.readFile('dndinfo.json', function(err, data) 
                console.log(time);
                fs.writeFile('dndinfo.json', JSON.stringify(editMessage, null, 2), (err) => 
                    if (err) console.error;
                    message.channel.send ('message written');

                );
            );
        
    ,
;

当我运行命令 '!te time 17:00' 时,整个 dndinfo.json 文件被替换为:

"17:00"

我知道这是因为我正在使用 fs.writeFile 但我不确定如何仅指定“时间”并进行更新?

【问题讨论】:

为什么不重写整个文件呢?它比修改它的一部分非常小而且更快。从 readfile 中获取数据,解析 Json,替换时间键的值并将其写回。只修改其中的一部分将需要您在流中读取、解析它、找到位置行/列以及要删除/替换多少个字符。这可以使用regex 来完成,但这违背了 json nodejs 功能的目的。 【参考方案1】:

您可能想先编辑它的内容,而不是将JSON.stringify(editMessage, null, 2) 写入您的 JSON。

您可以使用data.replace() 方法替换文件的内容。

您可以参考这个答案以获得完整的报道: https://***.com/a/14181136/4865314

【讨论】:

【参考方案2】:

没有实用的方法可以只更新文件中的一段 JSON。只是格式类型或磁盘上的布局不实用。

更新文件中的内容的常用机制是写出整个 JSON 结构并进行适当的修改,用新内容替换文件的原始内容。如果您还没有执行此操作所需的所有内容,那么您将首先读入文件的内容,进行更改,然后将其写出。

这是一个函数,它读取它,将 JSON 解析为 javascript 对象,修改该对象,转换回 JSON 并将其写出。

const fsp = require('fs').promises;

async function udpateTimeInFile(time) 
    try 
        let data = await fsp.readFile('dndinfo.json');
        let obj = JSON.parse(data);

        // set whatever property or properties in the object that you are trying to change
        obj.time = time;

        await fsp.writeFile('dndinfo.json', JSON.stringify(obj);
        message.channel.send('message written');
      catch(e) 
        // error handling here
        console.log(e);
        message.channel.send('error sending message');
        throw e;      // make sure caller can see the message
     

【讨论】:

【参考方案3】:

由于文件的工作方式,您可以更改文件部分的唯一方法是将其内容替换为完全相同大小的内容。此外,您的文件系统可能以 8K 块写入磁盘控制器,因此没有 /point/ 来执行此操作,除非在非常特殊的情况下。

除了重写整个文件之外,您还需要考虑如果文件变短,或者机器上有其他进程同时读取(或写入!)文件会发生什么。

对于这组问题,一个非常常见的通用解决方案是将新文件写入同一目录中的新文件名,然后在旧文件之上重命名。这意味着读取您的文件的其他进程将获取新文件或旧文件,这几乎总是比“对半”解决方案更受欢迎。

今天出现这个问题很有趣,因为我今天早些时候恰好写了这个例程,为我的调试器存储 REPL 历史记录。如果您想通读完整的 [但经过轻微测试的] 实现,请查看

https://github.com/wesgarland/niim/blob/cfb64356c4e5a9394ba2e2b0e82b2f3bf2ba0305/lib/niim.js#L373 和 https://github.com/wesgarland/niim/blob/cfb64356c4e5a9394ba2e2b0e82b2f3bf2ba0305/lib/niim.js#L459

这些对于堆栈溢出注释来说太大了,但我提供了一个指向该文件特定版本的静态链接,它应该会持续数年。

【讨论】:

以上是关于使用 fs 修改 json 文件的单个部分,而不是覆盖整个内容的主要内容,如果未能解决你的问题,请参考以下文章

使用 fs 读取文件后解析 Json

当 XML SOAP 响应中存在单个元素时,JSON 对象而不是数组

Node.js:使用“fs”迭代目录时检查文件是不是为符号链接

带有对象的 fs.writefile 返回 [object object]

Jquery Ajax 要求下载 JSON 文件而不是进入成功块

如何使用 JSON 路径获取 JSON 字符串的一部分而不是 JToken?