V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
molvqingtai
V2EX  ›  Node.js

如何修改 JSON string 中的值,只有改动部分产生 diff

  •  1
     
  •   molvqingtai · 2023-02-11 16:51:10 +08:00 · 2988 次点击
    这是一个创建于 686 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如我有这样一个 JSON

    {
      "pages": ["pages/index/index"],
      "subpackages": [
        {
          "name": "A",
          "pages": ["innerModule/pages/index/index"]
        },
        {
          "name": "B",
          "pages": ["innerModule/pages/index/index"]
        }
      ]
    }
    

    需要实现一个函数找到 subpackages 下面 name === "xxx" 的 object ,然后替换当前的 object ,没有找到就 push 到 subpackages

    使用 JSON.parse 很简单,但是会丢失格式信息,导致在 CI 中产生额外的 diff ,有什么很好处理这种场景的 AST 库推荐也行

    第 1 条附言  ·  2023-02-13 10:35:40 +08:00
    感谢大家回复,已使用 #9 的 chatgpt 答案,改吧改吧解决了
    23 条回复    2023-02-13 10:35:16 +08:00
    justdoit123
        1
    justdoit123  
       2023-02-11 16:58:44 +08:00   ❤️ 1
    预期的情况有两种:
    1. 当你需要改动的时候,一定会产生 diff
    2. 如果没有改动,则不能产生 diff

    我的方案:直接用 JSON.parse/stringify 。你说的那种情况应该只有在原本的 JSON 字符串不是用 JSON.stringify 的情况下会产生。你可以先统一把已存在的文件全部重新格式化一遍,后续就不会产生 diff 。
    bagel
        2
    bagel  
       2023-02-11 17:01:03 +08:00
    key 排个序不就好了
    molvqingtai
        3
    molvqingtai  
    OP
       2023-02-11 17:07:55 +08:00
    @mistkafka 我只想有改动的字段产生 diff ,因为一些流程的原因,没办法吧整个文件全部 parse 再 format 一遍
    klo424
        6
    klo424  
       2023-02-11 17:13:20 +08:00   ❤️ 1
    他的锚点不好用,重发[笑哭]

    console.log(JSON.stringify({ a: 2 }, null, " "));
    /*
    {
    "a": 2
    }
    */

    console.log(JSON.stringify({ uno: 1, dos: 2 }, null, "\t"));
    /*
    {
    "uno": 1,
    "dos": 2
    }
    */
    molvqingtai
        7
    molvqingtai  
    OP
       2023-02-11 17:13:27 +08:00
    @klo424 先 parse => stringify , 现在的问题是在 pasre 处就丢失格式信息了
    tool2d
        8
    tool2d  
       2023-02-11 17:18:25 +08:00   ❤️ 1
    这种情况适合用 line parse 功能。

    先把{"name": "A" .. }这个对象片段包含的连续行提取出来,别的行号内容都保留下来。

    只序列化和反序列化这一小部分的文件内容,然后再拼装输出一个完整文件。相当于自己手动局部对象 JSON.parse/stringify ,而不是全局。
    zictos
        9
    zictos  
       2023-02-11 17:19:55 +08:00   ❤️ 1
    chatgpt 的答案能参考吗?
    推荐使用 jsonc-parser 库,它可以解析带注释的 JSON ,然后根据需要更新 JSON 对象并生成新的 JSON string 。下面是一个示例代码,可以实现题目要求的功能:

    const { parseTree, findNodeAtLocation, applyEdits, modify } = require("jsonc-parser");

    function updatePackage(jsonStr, packageName, packageData) {
    const ast = parseTree(jsonStr);
    const subpackagesNode = findNodeAtLocation(ast, ["subpackages"]);
    const packageIndex = subpackagesNode.children.findIndex(
    (child) => findNodeAtLocation(child, ["value", "name"]).value === packageName
    );
    if (packageIndex !== -1) {
    const packageNode = subpackagesNode.children[packageIndex];
    const edits = modify(jsonStr, packageNode.offset, packageNode.length, JSON.stringify(packageData));
    return applyEdits(jsonStr, edits);
    } else {
    const edits = modify(jsonStr, subpackagesNode.offset + subpackagesNode.length - 1, 0, `,${JSON.stringify(packageData)}`);
    return applyEdits(jsonStr, edits);
    }
    }

    const jsonStr = `{
    "pages": ["pages/index/index"],
    "subpackages": [
    {
    "name": "A",
    "pages": ["innerModule/pages/index/index"]
    },
    {
    "name": "B",
    "pages": ["innerModule/pages/index/index"]
    }
    ]
    }`;

    console.log(updatePackage(jsonStr, "C", { name: "C", pages: ["pages/C/index"] }));

    在上面的代码中,updatePackage 函数接收一个 JSON string 、需要更新的包名和包数据。它首先使用 jsonc-parser 解析 JSON string ,然后在 AST 中查找 subpackages 节点。然后它在 subpackages 的子节点中查找与给定包名匹配的节点,如果找到了就将它替换为给定的包数据,否则就将包数据添加到 subpackages 的末尾。最后,它将生成的新 JSON string 返回。

    由于使用 jsonc-parser 解析 JSON string 时可以保留格式信息,因此在 CI 中就不会产生额外的 diff 了。
    klo424
        10
    klo424  
       2023-02-11 17:24:57 +08:00
    @molvqingtai #7 你好像没看懂我什么意思?你看#6 的代码,stringify 之前是没有格式的,加了后面的参数就有了格式。你手写的格式跟他一样就行了。
    molvqingtai
        11
    molvqingtai  
    OP
       2023-02-11 17:26:16 +08:00
    @zictos 感谢,我试试这个代码,今天也用了 V 站上面免登录的 chatgpt ,问了半天也没问出你这样的结果,难道需要走什么流程
    klo424
        12
    klo424  
       2023-02-11 17:29:17 +08:00   ❤️ 1
    你试一下好吧,根本不需要什么库[笑哭]

    console.log(JSON.stringify({uno:1,dos:[2,3],dns:{wf:{bbq:'5'},aec:4}}, null, "\t"))

    {
    "uno": 1,
    "dos": [
    2,
    3
    ],
    "dns": {
    "wf": {
    "bbq": "5"
    },
    "aec": 4
    }
    }
    klo424
        13
    klo424  
       2023-02-11 17:29:49 +08:00
    v2 把格式过滤掉了,发不出格式。
    molvqingtai
        14
    molvqingtai  
    OP
       2023-02-11 17:33:32 +08:00
    @klo424 感谢你的热情,但是我不能把已有的 JSON 都格式化一遍,而且输入的 object 格式也不一定和原 JSON 一致,总结就是想达到就像手动编辑一样的 diff 效果
    molvqingtai
        15
    molvqingtai  
    OP
       2023-02-11 17:33:57 +08:00
    @klo424 v2 支持 markdown ....
    klo424
        16
    klo424  
       2023-02-11 17:35:08 +08:00 via iPhone
    评论不支持
    klo424
        17
    klo424  
       2023-02-11 17:40:25 +08:00 via iPhone
    那我觉得你只能用正则写了,其他的插件也会有自己的格式,都有相同的问题。
    zictos
        18
    zictos  
       2023-02-11 17:43:38 +08:00
    @molvqingtai #11 别人做的是第三方的,调用 api 的,api 的跟目前官方网页版不一样,一点都不智能一点都不好用。反正我试了基本很多答案都没法用,而官方网页版每次的回答都很智能很有针对性
    lisongeee
        19
    lisongeee  
       2023-02-11 21:30:53 +08:00   ❤️ 1
    <script src="https://gist.github.com/lisonge/51f6a7e6118a3412ecc226d20ea4fa0d.js"></script>

    实现了找到替换的功能,使用的库是 acorn(解析 ast 获取位置) 和 magic-string(更新字符)
    lisongeee
        20
    lisongeee  
       2023-02-11 21:39:08 +08:00   ❤️ 1
    caotian
        21
    caotian  
       2023-02-12 21:22:59 +08:00   ❤️ 1
    试试 json patch
    libook
        22
    libook  
       2023-02-13 10:25:15 +08:00   ❤️ 1
    取决于你原 JSON 文本是如何产生的。

    如果你可以控制原 JSON 文本的生成过程,你可以给字段名进行递归排序,然后利用 stringify 的第 2 、3 位参数(去看一下 stringify 的文档)来格式化生成。后续修改也用相同方式处理,就可以得到除了修改部分一致的 JSON 文本。这个概念通常被称为 json stable stringify ,有一些现成的库可以用。

    如果你不能控制原 JSON 文本的生成过程,我没有想到什么好方法,可能只能用正则去匹配和修改文本中要改的那部分了。
    molvqingtai
        23
    molvqingtai  
    OP
       2023-02-13 10:35:16 +08:00
    感谢大家回复,已使用 #9 的 chatgpt 答案,改吧改吧解决了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   941 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 22:24 · PVG 06:24 · LAX 14:24 · JFK 17:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.