Использование cJSON для чтения в массиве JSON

Я пытаюсь использовать библиотеку cJSON, написанную Dave Gamble, для чтения в следующем массиве JSON:

"items": 
[
    {
        "name": "command",
        "index": "X",
        "optional": "0"
    },
    {
        "name": "status",
        "index": "X",
        "optional": "0"
    }
]

Считая документацию я нашел способы читать в отдельных объектах, но ничего не касалось массивов, и я не мог догадаться, как сделать это из приведенных примеров.

Вот что я пытаюсь:

cJSON* request_json = NULL;
cJSON* items = cJSON_CreateArray();
cJSON* name = NULL;
cJSON* index = NULL;
cJSON* optional = NULL;

request_json = cJSON_Parse(request_body);

items = cJSON_GetObjectItem(request_json, "items");

name = cJSON_GetObjectItem(items, "name");
index = cJSON_GetObjectItem(items, "index");
optional = cJSON_GetObjectItem(items, "optional");

Я знаю, что это неправильно, и не только потому, что он не работает, но я не могу понять, как сделать это правильно.

Очевидно, мне нужно будет зациклить процесс чтения во всех элементах для каждого индекса массива. Я понятия не имею, как это сделать, потому что я не знаю, где я должен использовать индексы в этом коде, или если это даже правильный старт. Существует cJSON_GetArrayItem(), но он принимает только число (предположительно индекс) и строку, чтобы указать, какое поле оно хочет.

Ответ 1

Документ упоминает о parse_object().

Я думаю, что это то, что вам нужно сделать.

void parse_object(cJSON *root)
{
  cJSON* name = NULL;
  cJSON* index = NULL;
  cJSON* optional = NULL;

  int i;

  cJSON *item = cJSON_GetObjectItem(items,"items");
  for (i = 0 ; i < cJSON_GetArraySize(item) ; i++)
  {
     cJSON * subitem = cJSON_GetArrayItem(item, i);
     name = cJSON_GetObjectItem(subitem, "name");
     index = cJSON_GetObjectItem(subitem, "index");
     optional = cJSON_GetObjectItem(subitem, "optional"); 
  }
}

Вызвать эту функцию как

request_json = cJSON_Parse(request_body);
parse_object(request_json);

Ответ 2

Если вы хотите работать немного быстрее, это выглядит так:

void parse_array(cJSON *array)
{
  cJSON *item = array ? array->child : 0;
  while (item)
  {
     cJSON *name = cJSON_GetObjectItem(item, "name");
     cJSON *index = cJSON_GetObjectItem(item, "index");
     cJSON *optional = cJSON_GetObjectItem(item, "optional"); 

     item=item->next;
  }
}

Это позволяет избежать стоимости O (n ^ 2), которую правильно указывает RBerteig.

Позвонить с помощью:

parse_array(cJSON_GetObjectItem(cJSON_Parse(request_body),"items"));

Ответ 3

IMHO, это один из примеров случая, когда вы должны вскрыть инкапсуляцию библиотеки и напрямую работать с ней структурой данных объекта. cJSON.h определяет основной объект как следующий struct:

/* The cJSON structure: */
typedef struct cJSON {
    struct cJSON *next,*prev;   /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
    struct cJSON *child;        /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */

    int type;                   /* The type of the item, as above. */

    char *valuestring;          /* The item string, if type==cJSON_String */
    int valueint;               /* The item number, if type==cJSON_Number */
    double valuedouble;         /* The item number, if type==cJSON_Number */

    char *string;               /* The item name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;

(Можно было бы смириться с некоторыми вариантами именования, которые автор сделал, конечно, но хорошее именование трудно.)

Главное отметить, что как объекты JSON, так и массивы JSON имеют ненулевое значение child, что указывает на двусвязный список их детей. Дети объектов JSON также имеют ненулевые string поля, которые содержат имя поля, связанное с этим дочерним элементом.

Итак, чтобы в общем случае выполнить итерацию по массиву JSON Array ja в O (n), вызывая функцию для каждого элемента, вы пишете что-то вроде этого:

cJSON_ForEachItem(cJSON *ja, int (*f)(cJSON *ja, int i, cJSON *jchild)) 
{
    cJSON *jchild;
    int i;
    for (jchild=ja->child, i=0; jchild; jchild=jchild->next, ++i) {
        // do something here with the ith child...
        if (f(ja, i, jchild))
            break;
    }
}

Так как объекты и массивы отличаются друг от друга только при наличии имен для каждого дочернего элемента, эта функция также будет перебирать поля объекта. Обратный вызов может сказать, потому что ja->type будет либо cJSON_Array, либо cJSON_Object, а jchild->string также будет не нулевым для объектов.

Выполнение одной и той же итерации путем вызова cJSON_GetArraySize() и использование cJSON_GetArrayItem() будет иметь порядок O (n ^ 2), потому что каждый раз нужно пересекать связанный список, чтобы найти n-й элемент.

Возможно, cJSON должен включать в себя некоторые общие функции ForEach, но это может означать начало значительного объема охвата области видимости от его заявленной первоначальной цели - быть "самым тугим возможным парсером, с которым вы можете выполнить свою работу с помощью".

Ответ 4

Моя догадка (не прочитав спецификацию и немного ржавшую с C):

request_json = cJSON_Parse(request_body);

items = cJSON_GetObjectItem(request_json, "items");
for (int i = 0; i < max; i++) {  // Presumably "max" can be derived from "items" somehow

    cJSON* item = cJSON_GetArrayItem(items, i);

    name = cJSON_GetObjectItem(item, "name");
    index = cJSON_GetObjectItem(item, "index");
    optional = cJSON_GetObjectItem(item, "optional");

    // Stash above info somewhere
}