遍历和修改 C json 字符串

问题描述

在 C 中遍历和修改 JSON 字符串的正确方法是什么?

具体来说,我有一个字符串,body_buf。打印出来时

print("length: %d\n%.*s\n",body_len,body_buf);

看起来像这样:

length: 113
{"field1":"something","whatever":10,"description":"body","id":"random","__oh__":{"session":"12345678jhgfdrtyui"}}

一个更复杂的 body_buf 可能如下所示:

{"status":1,"query":{},"proc":{"memory":{"total":17177939968,"cmax":18363625472,"amax":20000000000},"cpu":{"cores":[0.788,0.132,0.319,2.951,10.111,3.309,1.43,0.8,2.705,4.203,2.32,2,0.019,0.172,0.247,3.888,0.282,0.423,5.254,0.258,0.009,0.369,3.277,0.048,0.283,7.574,3.086,1.592,0.191,0.166,4.348,0.391,0.085,0.25,7.12,4.927,3.671,1.147,3.216,4.628,0.131,0.995,0.744,4.252,4.022,3.505,3.758,3.491],"total":108.886,"limit":800},"disk":{"used":20170,"limit":50000,"io_limit":500}}}

我想根据以下规则简化 body_buf(也可以兼作删除敏感信息),只修改值,而不修改任何键:

  1. 字符串成为字符串的长度。
  2. 字符串数组变为 [array_len,max_len,min_len]。
  3. 数字数组变为 [array_len,max,min]。

我不熟悉在 C 中使用 JSON 字符串。这样做的最佳方法是什么?

我可以将 body_buf 视为一个字符串并遍历它,修改“:”之后的任何内容,因为这些肯定是我可能修改的值,具体取决于类型。对于数组,我需要跟踪夹在“[”和“]”之间的任何内容。这可以工作,但似乎不是很简单。

或者,也许将 body_buf 转换为 JSON 类型,然后遍历嵌套结构。但后来我也必须修改它。我还没有找到使用 json-c 或其他方式遍历和修改(或通过某种深拷贝创建一个新对象?)JSON 对象的 C 示例(这会很有帮助)。

除了细节(上面的规则,1-3),这应该是一个比较常见的操作——遍历和修改。因此,对于那些更了解 json-c 或 JSON 操作的复杂性和良好/标准实践的人来说,我正在寻找一些指针。

再说一次,我有 json-c:

#include "cJSON.h"
#include "cJSON_Utils.h"
#include <libjson/json.h>
#include <libjson/json_tokener.h>

到目前为止,我查看的相关信息包括

https://gist.github.com/alan-mushi/19546a0e2c6bd4e059fd

How to get json values after json_tokener_parse()?

Parsing deeply nested JSON key using json-c

解决方法

我不知道“简化”json 会有多大用处。 第一次在 c 中使用 json 可能会很吓人。

我喜欢 cJSON 库,它轻巧、便携且稳定。 它具有良好的测试覆盖率,并且许可证是 MIT。

我认为使用库 cJSON 的这段代码可以满足您的要求:

#include <cjson/cJSON.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <float.h>

const char json1[] = "{\"field1\":\"something\",\"whatever\":10,\"description\":\"body\",\"id\":\"random\",\"__oh__\":{\"session\":\"12345678jhgfdrtyui\"}}";
const char json2[] = "{\"status\":1,\"query\":{},\"proc\":{\"memory\":{\"total\":17177939968,\"cmax\":18363625472,\"amax\":20000000000},\"cpu\":{\"cores\":[0.788,0.132,0.319,2.951,10.111,3.309,1.43,0.8,2.705,4.203,2.32,2,0.019,0.172,0.247,3.888,0.282,0.423,5.254,0.258,0.009,0.369,3.277,0.048,0.283,7.574,3.086,1.592,0.191,0.166,4.348,0.391,0.085,0.25,7.12,4.927,3.671,1.147,3.216,4.628,0.131,0.995,0.744,4.252,4.022,3.505,3.758,3.491],\"total\":108.886,\"limit\":800},\"disk\":{\"used\":20170,\"limit\":50000,\"io_limit\":500}}}";
const char json3[] = "{\"Name\":\"Tom\",\"Age\":18,\"Address\":\"California\",\"arr\":[1,3,4,5]}";

static void simplifyArray(cJSON *input,cJSON *output)
{  
    cJSON *item;
    size_t noElems = 0;
    
    if (cJSON_IsString(cJSON_GetArrayItem(input,0))) {
        size_t max,min;
        max = 0;
        min = UINT_MAX;
        cJSON_ArrayForEach(item,input) {
            noElems++;
            size_t len = strlen(cJSON_GetStringValue(item));
            if (len > max) max = len;
            if (len < min) min = len;
        }
        cJSON *newArray = cJSON_AddArrayToObject(output,input->string);
        cJSON_AddItemToArray(newArray,cJSON_CreateNumber(noElems));
        cJSON_AddItemToArray(newArray,cJSON_CreateNumber(max));
        cJSON_AddItemToArray(newArray,cJSON_CreateNumber(min));

    } else if (cJSON_IsNumber(cJSON_GetArrayItem(input,0))) {
        double max,min;
        max = -DBL_MAX;
        min = DBL_MAX;
        cJSON_ArrayForEach(item,input) {
            noElems++;
            double value = item->valuedouble;
            if (value > max) max = value;
            if (value < min) min = value;
        }
        cJSON *newArray = cJSON_AddArrayToObject(output,cJSON_CreateNumber(min));
    }
}

static void simplify(cJSON *input,cJSON *output)
{
    cJSON *elem;
    for (elem = input; elem != NULL; elem = elem->next) {
        if (cJSON_IsString(elem)) {
            cJSON_AddNumberToObject(output,elem->string,strlen(cJSON_GetStringValue(elem)));
        } else if (cJSON_IsArray(elem)) {
            simplifyArray(elem,output);
        } else if (cJSON_IsObject(elem)) {
            cJSON *newOutput = cJSON_AddObjectToObject(output,elem->string);
            simplify(elem->child,newOutput);
        } else {
            cJSON *dup = cJSON_Duplicate(elem,true);
            cJSON_AddItemToObject(output,dup);
        }
    }
}

static void simplifyAndPrint(const char *json)
{
    cJSON *input = cJSON_Parse(json);
    cJSON *output = cJSON_CreateObject();
    simplify(input->child,output);
    printf("%s\n",cJSON_PrintUnformatted(output));
    cJSON_Delete(input);
    cJSON_Delete(output);
}

int main()
{
    simplifyAndPrint(json1);
    simplifyAndPrint(json2);
    simplifyAndPrint(json3);
    return 0;
}

输出:

{"field1":9,"whatever":10,"description":4,"id":6,"__oh__":{"session":18}}
{"status":1,"query":{},"proc":{"memory":{"total":17177939968,"cmax":18363625472,"amax":20000000000},"cpu":{"cores":[48,0.009],"total":108.886,"limit":800},"disk":{"used":20170,"limit":50000,"io_limit":500}}}
{"Name":3,"Age":18,"Address":10,"arr":[5,5,1]}

在上面的例子中,我倾向于不要改变输入的 JSON,如果你不关心这个,你可以使用函数 cJSON_ReplaceItemInObject 来替换节点。

P.S.:我假设数组只包含字符串和数字,不要混用,因为没有处理其他数组配置的规则。

P.S.2:此代码使用的是 Ubuntu 20.04 中的库版本,如果您从 GitHub 下载该库,该版本将包含更多功能。