cJSON代码解读

1、背景

cJSON用了很久,但是对它一直不太了解。这次向添加对long long类型的支持,一直出问题。因为有以前添加两位小数float的经历,我觉得会很轻松,没想到翻车了。于是有了这边文档,阅读了部分博主对cJSON的解析,给出自己的体悟。

1.1 参考文档

【万字详解】cJSON解析-CSDN博客

2 从使用者角度分析

2.1 数据结构上分析

cJSON在使用上来说有两种:

1、将json字符串输入得到key-value;

2、将key-value输入得到一个json字符串;

两者的桥梁就是cJSON提供结构体cJSON,由该结构体通过链表形成一个树来表征一个JSON。

一个cJSON结构体是对JSON数据的抽象。

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

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

    /* The item's string, if type==cJSON_String  and type == cJSON_Raw */
    char *valuestring;
    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
    int valueint;
    /* The item's number, if type==cJSON_Number */
    double valuedouble;
    /* The item's number, if type==cJSON_Int64 */
    long long valueint64;
    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
    char *string;
  } cJSON;

next、prev是链表的后继和前驱---兄弟节点;

child是子节点;

string 是该节点的key,而value 可以根据type类型来决定,是valuestring、valueint、valuedouble、valueint64。

接下去就是从使用角度分析,分别是组包JSON和解析JSON字符串两个角度出发。

2.2组包JSON

涉及到的函数如下所述,由创建cJSON、添加子节点、转成字符串、删除cJSON等过程。

cpp 复制代码
  /*创建节点*/
  CJSON_PUBLIC(cJSON *)
  cJSON_CreateArray(void);
  CJSON_PUBLIC(cJSON *)
  cJSON_CreateObject(void);
   /*添加子节点*/
  CJSON_PUBLIC(cJSON *)
  cJSON_AddNumberToObject(cJSON *const object, const char *const name, const double number);
  CJSON_PUBLIC(cJSON *)
  cJSON_AddDoubleToObject(cJSON *const object, const char *const name, const double number);
  CJSON_PUBLIC(cJSON *)
  cJSON_AddInt64ToObject(cJSON *const object, const char *const name, const long long number);
  CJSON_PUBLIC(cJSON *)
  cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string);
   /*添加子节点2*/  
  CJSON_PUBLIC(void)
  cJSON_AddItemToArray(cJSON *array, cJSON *item);
  CJSON_PUBLIC(void)
  cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
   /*cJSON对象转字符串*/   
  /* Render a cJSON entity to text for transfer/storage. */
  CJSON_PUBLIC(char *)
  cJSON_Print(const cJSON *item);
  CJSON_PUBLIC(void)
  cJSON_Minify(char *json);
 /*删除cJSON对象*/   
  /* Delete a cJSON entity and all subentities. */
  CJSON_PUBLIC(void)
  cJSON_Delete(cJSON *c);

每个过程看一两个函数实现

2.2.1 创建节点指定类型的节点

cpp 复制代码
/* Internal constructor. */
static cJSON *cJSON_New_Item(const internal_hooks *const hooks)
{
    cJSON *node = (cJSON *)hooks->allocate(sizeof(cJSON));
    if (node)
    {
        memset(node, '\0', sizeof(cJSON));
    }

    return node;
}
CJSON_PUBLIC(cJSON *)
cJSON_CreateObject(void)
{
    cJSON *item = cJSON_New_Item(&global_hooks);
    if (item)
    {
        item->type = cJSON_Object;
    }

    return item;
}

申请一个新的节点,成功则将类型设置成创建的类型,返回该节点。

这里涉及到一个类型的说法

cpp 复制代码
/* cJSON Types: */
#define cJSON_Invalid (0)            /*无实际意义值,初始化节点时状态*/
#define cJSON_False (1 << 0)        /*布尔类型false*/
#define cJSON_True (1 << 1)        /*布尔类型true*/
#define cJSON_NULL (1 << 2)        /*空类型NULL*/
#define cJSON_Number (1 << 3)        /*数值类型*/
#define cJSON_String (1 << 4)        /*字符串类型*/
#define cJSON_Array (1 << 5)        /*列表类型, child存储值*/
#define cJSON_Object (1 << 6)        /*对象类型, child存储值*/
#define cJSON_Raw (1 << 7) /* raw json 表示valuestring中以\0结尾字符数据的任何类型*/
#define cJSON_Double (1 << 8)         /*浮点类型*/   
#define cJSON_Int64 (1 << 9)        /*long long int类型*/ 

#define cJSON_Valid_Flags (0x03FF)
/*两个标志*/
#define cJSON_IsReference (512)     /*标记child指向或valuestring不属于该节点,无需释放*/
#define cJSON_StringIsConst (1 << 10)    /*string成员是一个常量,无需释放*/

如上述代码所述,用位来标记是什么类型的。

另外还有两个标志,分别

(1)表示该节点是个引用,其中的child和valuestring不属于该节点,释放时注意;

(2)表示该节点的string成员指向的是个常量,无需释放。

2.2.2 删除节点

cpp 复制代码
/* Delete a cJSON structure. */
CJSON_PUBLIC(void)
cJSON_Delete(cJSON *item)
{
    cJSON *next = NULL;
    while (item != NULL)    /*循环直至链表释放完*/
    {
        next = item->next;    /*链表下一个兄弟节点*/
        printf("item type 0x%x\n", item->type);
        /*节点不带引用标志,且有子节点,则释放子节点*/
        if (!(item->type & cJSON_IsReference) && (item->child != NULL))
        {
            cJSON_Delete(item->child);
        }
        /*节点不带引用标志,且有valuestring,则释放valuestring*/
        if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
        {
            global_hooks.deallocate(item->valuestring);
        }
         /*节点不是常量标志,则释放string*/
        if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
        {
            global_hooks.deallocate(item->string);
        }
        /*释放节点本身*/
        global_hooks.deallocate(item);
        item = next;    /*item=下一个兄弟节点*/
    }
}

具体看代码注释

和创建节点中标志位一一对应。释放节点下子节点和一切动态分配的资源。

2.2.3添加子节点1

cpp 复制代码
/*
* @fn add_item_to_object
* @param object The object to add to.
* @param string The string in key.
* @param item The item to add.
* @param hooks The hooks to use.
* @param constant_key Whether the key is a constant or not.
* @return true on success, false on failure.
*/
static cJSON_bool add_item_to_object(cJSON *const object, const char *const string, cJSON *const item, const internal_hooks *const hooks, const cJSON_bool constant_key)
{
    if ((object == NULL) || (string == NULL) || (item == NULL))
    {
        return false;
    }

    if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
    {
        hooks->deallocate(item->string);
    }

    if (constant_key)
    {
        item->string = (char *)cast_away_const(string);
        item->type |= cJSON_StringIsConst;
    }
    else
    {
        char *key = (char *)cJSON_strdup((const unsigned char *)string, hooks);
        if (key == NULL)
        {
            return false;
        }

        item->string = key;
        item->type &= ~cJSON_StringIsConst;
    }

    return add_item_to_array(object, item);
}


static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
{
    cJSON *child = NULL;

    if ((item == NULL) || (array == NULL))
    {
        return false;
    }

    child = array->child;

    if (child == NULL)
    {
        /* list is empty, start new one */
        array->child = item;
    }
    else
    {
        /* append to the end */
        while (child->next)
        {
            child = child->next;
        }
        suffix_object(child, item);
    }

    return true;
}

/*
* @fn cJSON_AddStringToObject
* @param object The object to add to.
* @param name The name of the item to add.
* @param string The string to add.
* @return The new item, or NULL on failure.
*/
CJSON_PUBLIC(cJSON *)
cJSON_AddStringToObject(cJSON *const object, const char *const name, const char *const string)
{
    cJSON *string_item = cJSON_CreateString(string);
    if (add_item_to_object(object, name, string_item, &global_hooks, false))
    {
        return string_item;
    }

    cJSON_Delete(string_item);
    return NULL;
}

cJSON_AddStringToObject函数向object节点中添加key是name, value是string的子节点,先将value复制给了valuestring成员;

在add_item_to_object根据constant_key的值,给key赋了值,对type成员设置关于cJSON_StringIsConst标志的值;

add_item_to_array将子节点添加在父节点的child成员指向的链表下

2.2.4 添加子节点2

cpp 复制代码
CJSON_PUBLIC(void)
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)
{
    add_item_to_object(object, string, item, &global_hooks, false);
}

也调用了add_item_to_object,不再叙述

2.2.5 cJSON结构体转字符串

cpp 复制代码
static unsigned char *print(const cJSON *const item, cJSON_bool format, const internal_hooks *const hooks)
{
    static const size_t default_buffer_size = 256;
    printbuffer buffer[1];
    unsigned char *printed = NULL;

    memset(buffer, 0, sizeof(buffer));

    /* create buffer */
    buffer->buffer = (unsigned char *)hooks->allocate(default_buffer_size);
    buffer->length = default_buffer_size;
    buffer->format = format;
    buffer->hooks = *hooks;
    if (buffer->buffer == NULL)
    {
        goto fail;
    }

    /* print the value */
    if (!print_value(item, buffer))
    {
        goto fail;
    }
    update_offset(buffer);

    /* check if reallocate is available */
    if (hooks->reallocate != NULL)
    {
        printed = (unsigned char *)hooks->reallocate(buffer->buffer, buffer->offset + 1);
        buffer->buffer = NULL;
        if (printed == NULL)
        {
            goto fail;
        }
    }
    else /* otherwise copy the JSON over to a new buffer */
    {
        printed = (unsigned char *)hooks->allocate(buffer->offset + 1);
        if (printed == NULL)
        {
            goto fail;
        }
        memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));
        printed[buffer->offset] = '\0'; /* just to be sure */
        /* free the buffer */
        hooks->deallocate(buffer->buffer);
    }

    return printed;

fail:
    if (buffer->buffer != NULL)
    {
        hooks->deallocate(buffer->buffer);
    }

    if (printed != NULL)
    {
        hooks->deallocate(printed);
    }

    return NULL;
}
/* Render a cJSON item/entity/structure to text. */
CJSON_PUBLIC(char *)
cJSON_Print(const cJSON *item)
{
    return (char *)print(item, true, &global_hooks);
}

简单就是cJSON_Print

->print

->print_value

->realloc

->return printed;

核心看print_value

cpp 复制代码
/* Render a value to text. */
static cJSON_bool print_value(const cJSON *const item, printbuffer *const output_buffer)
{
    unsigned char *output = NULL;
    /*检查输入和输出参数*/
    if ((item == NULL) || (output_buffer == NULL))
    {
        return false;
    }
    /*排除标志影响,直接看item是什么类型的*/
    switch ((item->type) & cJSON_Valid_Flags)
    {
    case cJSON_NULL:
        output = ensure(output_buffer, 5);
        if (output == NULL)
        {
            return false;
        }
        strcpy((char *)output, "null");
        return true;

    case cJSON_False:
        output = ensure(output_buffer, 6);
        if (output == NULL)
        {
            return false;
        }
        strcpy((char *)output, "false");
        return true;

    case cJSON_True:
        output = ensure(output_buffer, 5);
        if (output == NULL)
        {
            return false;
        }
        strcpy((char *)output, "true");
        return true;

    case cJSON_Number:
        return print_number(item, output_buffer);
    case cJSON_Double:
        return print_double(item, output_buffer);
    case cJSON_Int64:
        return print_int64(item, output_buffer);
    case cJSON_Raw:
    {
        size_t raw_length = 0;
        if (item->valuestring == NULL)
        {
            if (!output_buffer->noalloc)
            {
                output_buffer->hooks.deallocate(output_buffer->buffer);
            }
            return false;
        }

        raw_length = strlen(item->valuestring) + sizeof("");
        output = ensure(output_buffer, raw_length);
        if (output == NULL)
        {
            return false;
        }
        memcpy(output, item->valuestring, raw_length);
        return true;
    }

    case cJSON_String:
        return print_string(item, output_buffer);

    case cJSON_Array:
        return print_array(item, output_buffer);

    case cJSON_Object:
        return print_object(item, output_buffer);

    default:
        return false;
    }
}

根据cJSON对象所述类型进行字符串化,我们关注一个object、array、num这三种类型,string类型用脚趾都能想到。

cpp 复制代码
/* Render an object to text. */
static cJSON_bool print_object(const cJSON *const item, printbuffer *const output_buffer)
{
    unsigned char *output_pointer = NULL;
    size_t length = 0;
    cJSON *current_item = item->child;

    if (output_buffer == NULL)
    {
        return false;
    }

    /* Compose the output: */
    length = (size_t)(output_buffer->format ? 2 : 1); /* fmt: {\n */
    output_pointer = ensure(output_buffer, length + 1);
    if (output_pointer == NULL)
    {
        return false;
    }
    /*花括号前部分*/
    *output_pointer++ = '{';
    output_buffer->depth++;
    if (output_buffer->format)
    {
        *output_pointer++ = '\n';
    }
    output_buffer->offset += length;

    while (current_item)
    {
        if (output_buffer->format)
        {
            size_t i;
            output_pointer = ensure(output_buffer, output_buffer->depth);
            if (output_pointer == NULL)
            {
                return false;
            }
            for (i = 0; i < output_buffer->depth; i++)
            {
                *output_pointer++ = '\t';
            }
            output_buffer->offset += output_buffer->depth;
        }
         /*子节点的key*/
        /* print key */
        if (!print_string_ptr((unsigned char *)current_item->string, output_buffer))
        {
            return false;
        }
        update_offset(output_buffer);

        length = (size_t)(output_buffer->format ? 2 : 1);
        output_pointer = ensure(output_buffer, length);
        if (output_pointer == NULL)
        {
            return false;
        }
         /*key-value分隔符*/
        *output_pointer++ = ':';
        if (output_buffer->format)
        {
            *output_pointer++ = '\t';
        }
        output_buffer->offset += length;
        /*子节点value,调用print_value*/
        /* print value */
        if (!print_value(current_item, output_buffer))
        {
            return false;
        }
        update_offset(output_buffer);

        /* print comma if not last */
        length = (size_t)((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0));
        output_pointer = ensure(output_buffer, length + 1);
        if (output_pointer == NULL)
        {
            return false;
        }
        if (current_item->next)
        {
         /*如果子节点有下一个兄弟节点,加逗号*/
        /* print value */
            *output_pointer++ = ',';
        }

        if (output_buffer->format)
        {
            *output_pointer++ = '\n';
        }
        *output_pointer = '\0';
        output_buffer->offset += length;

        current_item = current_item->next;
    }

    output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);
    if (output_pointer == NULL)
    {
        return false;
    }
    if (output_buffer->format)
    {
        size_t i;
        for (i = 0; i < (output_buffer->depth - 1); i++)
        {
            *output_pointer++ = '\t';
        }
    }
    /*花括号后部分*/
    *output_pointer++ = '}';
    *output_pointer = '\0';
    output_buffer->depth--;

    return true;
}

object类型的cJSON对象输出字符串类似下图,具体格式细节还是根据format来控制

bash 复制代码
{
"child_key":"child_value"
}

format为true时,配合\t和深度来缩进完成格式化。

cpp 复制代码
/* Render an array to text */
static cJSON_bool print_array(const cJSON *const item, printbuffer *const output_buffer)
{
    unsigned char *output_pointer = NULL;
    size_t length = 0;
    cJSON *current_element = item->child;

    if (output_buffer == NULL)
    {
        return false;
    }

    /* Compose the output array. */
    /* opening square bracket */
    output_pointer = ensure(output_buffer, 1);
    if (output_pointer == NULL)
    {
        return false;
    }
    /*列表中括号前部分*/
    *output_pointer = '[';
    output_buffer->offset++;
    output_buffer->depth++;

    while (current_element != NULL)
    {    /*子节点的value*/
        if (!print_value(current_element, output_buffer))
        {
            return false;
        }
        update_offset(output_buffer);
        if (current_element->next)
        {
            length = (size_t)(output_buffer->format ? 2 : 1);
            output_pointer = ensure(output_buffer, length + 1);
            if (output_pointer == NULL)
            {
                return false;
            }
             /*有下一个对象,则添加列表对象间分隔符*/
            *output_pointer++ = ',';
            if (output_buffer->format)
            {
                *output_pointer++ = ' ';
            }
            *output_pointer = '\0';
            output_buffer->offset += length;
        }
        current_element = current_element->next;
    }

    output_pointer = ensure(output_buffer, 2);
    if (output_pointer == NULL)
    {
        return false;
    }
    /*中括号后部分*/
    *output_pointer++ = ']';
    *output_pointer = '\0';
    output_buffer->depth--;

    return true;
}

输出为

cpp 复制代码
[子节点值,子节点值]

这都是从结果来推到需求,写代码时从需求到结果,其实更好的分析方法是想自己该如何实现它。

cpp 复制代码
/* Render the number nicely from the given item into a string. */
static cJSON_bool print_number(const cJSON *const item, printbuffer *const output_buffer)
{
    unsigned char *output_pointer = NULL;
    double d = item->valuedouble;
    int length = 0;
    size_t i = 0;
    unsigned char number_buffer[26]; /* temporary buffer to print the number into */
    unsigned char decimal_point = get_decimal_point();
    double test;

    if (output_buffer == NULL)
    {
        return false;
    }

    /* This checks for NaN and Infinity */
    if ((d * 0) != 0)
    {
        length = sprintf((char *)number_buffer, "null");
    }
    else
    {
        /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
        length = sprintf((char *)number_buffer, "%1.15g", d);

        /* Check whether the original double can be recovered */
        if ((sscanf((char *)number_buffer, "%lg", &test) != 1) || ((double)test != d))
        {
            /* If not, print with 17 decimal places of precision */
            length = sprintf((char *)number_buffer, "%1.17g", d);
        }
    }

    /* sprintf failed or buffer overrun occured */
    if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
    {
        return false;
    }

    /* reserve appropriate space in the output */
    output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
    if (output_pointer == NULL)
    {
        return false;
    }

    /* copy the printed number to the output and replace locale
     * dependent decimal point with '.' */
    for (i = 0; i < ((size_t)length); i++)
    {
        if (number_buffer[i] == decimal_point)
        {
            output_pointer[i] = '.';

            continue;
        }

        output_pointer[i] = number_buffer[i];
    }
    output_pointer[i] = '\0';

    output_buffer->offset += (size_t)i;

    return true;
}

%1.15g

  • %:格式化输出的开始符号
  • 1.15:表示输出的总宽度为1,小数点后保留15位有效数字
  • g:以指数形式输出浮点数

按上述获取长度后,从double类型的valuedouble中转化为字符串

2.3 解析JSON

涉及到函数有

cpp 复制代码
  /*解析*/
  CJSON_PUBLIC(cJSON *)
  cJSON_Parse(const char *value);
  /*获取节点数目或节点*/
  CJSON_PUBLIC(int)
  cJSON_GetArraySize(const cJSON *array);
  /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */
  CJSON_PUBLIC(cJSON *)
  cJSON_GetArrayItem(const cJSON *array, int index);
  /* Get item "string" from object. Case insensitive. */
  CJSON_PUBLIC(cJSON *)
  cJSON_GetObjectItem(const cJSON *const object, const char *const string);

2.3.1 解析

cpp 复制代码
/* Parse an object - create a new root, and populate. */
CJSON_PUBLIC(cJSON *)
cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
{
    parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}};
    cJSON *item = NULL;

    /* reset error position */
    global_error.json = NULL;
    global_error.position = 0;

    if (value == NULL)
    {
        goto fail;
    }

    buffer.content = (const unsigned char *)value;
    buffer.length = strlen((const char *)value) + sizeof("");
    buffer.offset = 0;
    buffer.hooks = global_hooks;
    /*创建根节点*/
    item = cJSON_New_Item(&global_hooks);
    if (item == NULL) /* memory fail */
    {
        goto fail;
    }

    if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))
    {
        /* parse failure. ep is set. */
        goto fail;
    }

    /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
    if (require_null_terminated)
    {
        buffer_skip_whitespace(&buffer);
        if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0')
        {
            goto fail;
        }
    }
    if (return_parse_end)
    {
        *return_parse_end = (const char *)buffer_at_offset(&buffer);
    }

    return item;

fail:
    if (item != NULL)
    {
        cJSON_Delete(item);
    }

    if (value != NULL)
    {
        error local_error;
        local_error.json = (const unsigned char *)value;
        local_error.position = 0;

        if (buffer.offset < buffer.length)
        {
            local_error.position = buffer.offset;
        }
        else if (buffer.length > 0)
        {
            local_error.position = buffer.length - 1;
        }

        if (return_parse_end != NULL)
        {
            *return_parse_end = (const char *)local_error.json + local_error.position;
        }

        global_error = local_error;
    }

    return NULL;
}

CJSON_PUBLIC(cJSON *)
cJSON_Parse(const char *value)
{
    return cJSON_ParseWithOpts(value, 0, 0);
}

核心还是来到了parse_value

cpp 复制代码
/* Parser core - when encountering text, process appropriately. */
static cJSON_bool parse_value(cJSON *const item, parse_buffer *const input_buffer)
{
    if ((input_buffer == NULL) || (input_buffer->content == NULL))
    {
        return false; /* no input */
    }

    /* parse the different types of values */
    /* null */
    if (can_read(input_buffer, 4) && (strncmp((const char *)buffer_at_offset(input_buffer), "null", 4) == 0))
    {
        item->type = cJSON_NULL;
        input_buffer->offset += 4;
        return true;
    }
    /* false */
    if (can_read(input_buffer, 5) && (strncmp((const char *)buffer_at_offset(input_buffer), "false", 5) == 0))
    {
        item->type = cJSON_False;
        input_buffer->offset += 5;
        return true;
    }
    /* true */
    if (can_read(input_buffer, 4) && (strncmp((const char *)buffer_at_offset(input_buffer), "true", 4) == 0))
    {
        item->type = cJSON_True;
        item->valueint = 1;
        input_buffer->offset += 4;
        return true;
    }
    /* string */
    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"'))
    {
        return parse_string(item, input_buffer);
    }
    /* number */
    if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))
    {
        return parse_number(item, input_buffer);
    }
    /* array */
    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))
    {
        return parse_array(item, input_buffer);
    }
    /* object */
    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))
    {
        return parse_object(item, input_buffer);
    }

    return false;
}

针对各自类型的特点开始解析,根据这里也没有啥特殊的,最终都化为字符串和数值。

2.3.2 获取节点数目

获取子节点的个数,子节点链表中对象数目。

2.3.3 获取节点

只能获取子节点的信息,采取一级一级剥洋葱的方式。

3、添加对long long类型的支持

有博主是在cJSON_NUM类型下开了个子类型,逻辑也是相当清晰,本文在大的类型里添加。

没有啥难度,照葫芦画瓢,把类型和标志的意义搞懂就没出过BUG了。

相关推荐
小莞尔5 小时前
【51单片机】【protues仿真】基于51单片机四层电梯系统
单片机·嵌入式硬件
CFZPL5 小时前
使用江科大串口发送函数发送freertos的vTaskList出现跑飞
单片机
F133168929576 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·51单片机·硬件工程·pcb工艺
易享电子7 小时前
基于单片机电器断路器保护器系统Proteus仿真(含全部资料)
单片机·嵌入式硬件·fpga开发·51单片机·proteus
爱倒腾的老唐9 小时前
01、如何学习单片机
单片机·嵌入式硬件·学习
点灯小铭9 小时前
基于单片机的夹具压力控制系统设计
单片机·嵌入式硬件·mongodb·毕业设计·课程设计
雾削木15 小时前
stm32解锁芯片
javascript·stm32·单片机·嵌入式硬件·gitee
三佛科技-1341638421217 小时前
手持小风扇MCU方案,智能风扇方案设计开发
单片机·嵌入式硬件
btzhy18 小时前
STM32单片机:基本定时器应用:PWM 生成(STM32L4xx)
stm32·单片机·嵌入式硬件·基本定时器应用:pwm生成
guangshui51618 小时前
18006.STM32通过SPI读取LAN9253数据
stm32·单片机·嵌入式硬件