为什么打印出来的 cJSON type 值和头文件定义的不一样?
在 cJSON.h 里你可能看到类似这样的宏定义(这是为了描述“类型索引”):
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
背景与问题描述
在 cJSON.h
里你可能看到类似这样的宏定义(这是为了描述“类型索引”):
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6
#define cJSON_IsReference 256
你运行程序时,打印 item->type
却看到这样的数值:
-
cJSON_String
打印成16
-
cJSON_Number
打印成8
-
甚至还有
256
之类的值(IsReference
)
看起来“头文件里定义的数字”和运行时的 type
值“对不上”。所以你问:为什么会变成 8
、16
等?是不是经过了左移(bit shift)操作?
原因(核心)—— 类型是用“位标志(bit flags)”来存储的
关键点:头文件里给出的那组数字(0、1、2、3、4……)是“类型索引/编号”,而不是 item->type
在运行时的存储值。
cJSON
在内部把类型表示成“位掩码(bit mask)/标志(flags)”。也就是说在运行时它把“类型编号”转换为 1 << typeIndex
这样一个位值来存储:
-
cJSON_False
索引0
→ 存储位值1 << 0
=1
-
cJSON_True
索引1
→ 存储位值1 << 1
=2
-
cJSON_NULL
索引2
→ 存储位值1 << 2
=4
-
cJSON_Number
索引3
→ 存储位值1 << 3
=8
-
cJSON_String
索引4
→ 存储位值1 << 4
=16
-
...
还有一些额外的标志,比如 cJSON_IsReference
在头文件定义为 256
(等于 1 << 8
),这是为了把“引用”当作额外的位来表示(与类型位可以共存)。
所以,当你直接 printf("%d\n", item->type)
,你看到的是“位掩码”的值(如 8
、16
),而不是头文件那组“索引式”的数字(3、4)。
推荐的使用方式(三种稳妥做法)
-
优先使用 cJSON 提供的类型检查函数/宏(如果可用),例如
cJSON_IsString(item)
、cJSON_IsNumber(item)
,这样代码更清晰,不依赖内部实现细节(也更兼容未来 cJSON 的变动)。 -
如果要自己检查位值,使用位运算并加上移位(shift)逻辑,例如:
if (item->type & (1 << cJSON_String)) { /* String */ } if (item->type & (1 << cJSON_Number)) { /* Number */ }
-
或者把
item->type
的位值转换成“可读名称”(写一个映射函数),这样打印出的结果会直观很多。
可运行 Demo:演示类型索引与位掩码的关系,并给出映射函数
下面是一个完整的 C Demo。说明:
-
假设你把
cJSON.c
/cJSON.h
放在同一目录下(或已安装 cJSON 库)。 -
源文件名
demo.c
。 -
编译方式在说明里给出(gcc 方式)。
注意:如果你没有 cJSON 源文件,可以从官方仓库克隆(https://github.com/DaveGamble/cJSON),把
cJSON.h
和cJSON.c
放到 demo 同目录即可。
demo.c
/*
* demo.c
* 说明:
* - 演示 cJSON item->type 在运行时是位掩码(1 << typeIndex)
* - 提供一个把 typeFlag 转换为可读字符串的函数
*
* 编译:gcc demo.c cJSON.c -o demo
* 运行:./demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
/* 用于把位掩码转换成人可读的类型名(可能有多个标志并存) */
const char* cJSON_GetTypeNames(int typeFlag) {
static char buf[256];
buf[0] = '\0';
const char *names[] = {
"False", // index 0
"True", // index 1
"Null", // index 2
"Number", // index 3
"String", // index 4
"Array", // index 5
"Object" // index 6
};
int first = 1;
for (int i = 0; i <= 6; ++i) {
int mask = (1 << i); // runtime中的实际位掩码
if (typeFlag & mask) {
if (!first) strcat(buf, "|");
strcat(buf, names[i]);
first = 0;
}
}
// 检查 cJSON_IsReference(如果存在)
#ifdef cJSON_IsReference
if (typeFlag & cJSON_IsReference) {
if (!first) strcat(buf, "|");
strcat(buf, "IsReference");
first = 0;
}
#else
/* 头文件通常定义 cJSON_IsReference 为 256 (1<<8) */
if (typeFlag & (1 << 8)) {
if (!first) strcat(buf, "|");
strcat(buf, "IsReference");
first = 0;
}
#endif
if (buf[0] == '\0') return "Unknown/None";
return buf;
}
/* 辅助:打印单个 item 的信息 */
void print_item_info(const cJSON *item) {
if (!item) return;
printf("key = \"%s\"\n", item->string ? item->string : "(root child)");
printf(" raw item->type = %d\n", item->type);
printf(" type bits (hex) = 0x%X\n", item->type);
printf(" readable types = %s\n", cJSON_GetTypeNames(item->type));
/* 演示如何使用位移检测(推荐) */
if (item->type & (1 << cJSON_String)) {
printf(" -> Detected via (item->type & (1<<cJSON_String)): STRING\n");
}
if (item->type & (1 << cJSON_Number)) {
printf(" -> Detected via (item->type & (1<<cJSON_Number)): NUMBER\n");
}
if (item->type & (1 << cJSON_Array)) {
printf(" -> Detected via (item->type & (1<<cJSON_Array)): ARRAY\n");
}
if (item->type & (1 << cJSON_Object)) {
printf(" -> Detected via (item->type & (1<<cJSON_Object)): OBJECT\n");
}
/* 如果 cJSON 提供了检查宏,优先用宏(兼容性更好) */
#ifdef cJSON_IsString
if (cJSON_IsString((cJSON*)item)) {
printf(" -> cJSON_IsString macro says: STRING\n");
}
#endif
#ifdef cJSON_IsNumber
if (cJSON_IsNumber((cJSON*)item)) {
printf(" -> cJSON_IsNumber macro says: NUMBER\n");
}
#endif
printf("\n");
}
int main(void) {
/* 测试 JSON */
const char *json = "{"
"\"name\":\"Alice\","
"\"age\":30,"
"\"active\":true,"
"\"items\": [1, 2, 3],"
"\"obj\": {\"k\":\"v\"},"
"\"nullval\": null"
"}";
cJSON *root = cJSON_Parse(json);
if (!root) {
printf("parse error\n");
return 1;
}
printf("Parsed JSON. Iterate top-level children:\n\n");
for (cJSON *item = root->child; item != NULL; item = item->next) {
print_item_info(item);
}
/* 额外演示:某些内部函数/字段 */
printf("Note: cJSON header has defines like cJSON_String=%d, cJSON_Number=%d\n", cJSON_String, cJSON_Number);
printf("But runtime item->type stores the bitmask (1<<typeIndex)\n");
printf("So cJSON_String index=%d corresponds to runtime mask=1<<%d = %d\n\n", cJSON_String, cJSON_String, (1 << cJSON_String));
cJSON_Delete(root);
return 0;
}
编译与运行(步骤)
假设你已经把 demo.c
、cJSON.c
、cJSON.h
放在同一目录下:
gcc demo.c cJSON.c -o demo
./demo
预期输出(示例)
运行后你会看到类似这样的输出(注:数字可能随 cJSON 版本不同略有格式差异,但思路一致):
Parsed JSON. Iterate top-level children:
key = "name"
raw item->type = 16
type bits (hex) = 0x10
readable types = String
-> Detected via (item->type & (1<<cJSON_String)): STRING
-> cJSON_IsString macro says: STRING
key = "age"
raw item->type = 8
type bits (hex) = 0x8
readable types = Number
-> Detected via (item->type & (1<<cJSON_Number)): NUMBER
-> cJSON_IsNumber macro says: NUMBER
key = "active"
raw item->type = 2
type bits (hex) = 0x2
readable types = True
...
key = "items"
raw item->type = 32
type bits (hex) = 0x20
readable types = Array
key = "obj"
raw item->type = 64
type bits (hex) = 0x40
readable types = Object
key = "nullval"
raw item->type = 4
type bits (hex) = 0x4
readable types = Null
Note: cJSON header has defines like cJSON_String=4, cJSON_Number=3
But runtime item->type stores the bitmask (1<<typeIndex)
So cJSON_String index=4 corresponds to runtime mask=1<<4 = 16
你可以看到 raw item->type
是 16
、8
等(即 1<<4
与 1<<3
),而 cJSON_String
宏本身的数值仍是 4
——两者不是同一个“语义”。这就是你遇到的“数值不一致”的根本原因。
进一步的建议与实践要点
-
别直接比较
item->type == cJSON_String
:这样会失败,因为item->type
是位掩码(16),cJSON_String
是索引(4)。 -
优选使用库提供的宏/函数:如果
cJSON_IsString
/cJSON_IsNumber
等宏存在,就直接用它们,语义最清晰,也最兼容不同 cJSON 版本。 -
如果自己判断,记住左移:用
(item->type & (1 << cJSON_String))
来检测。 -
映射函数便于调试:调试时把
item->type
显示成可读的名字,能快速定位问题。 -
注意复合标志:一个 item 可能同时带有
IsReference
,因此一定用位检查而不是等于比较。 -
如果你维护一个库,写封装函数:把类型判断封装成函数,避免到处重复写位运算。
总结
头文件里看到的 cJSON_String = 4
是“类型的索引”,而 item->type
在运行时是以“位掩码(1 << index)”的形式存储的;因此直接打印 item->type
会得到 1<<4 = 16
这样的数值。正确做法是使用 cJSON 提供的 cJSON_IsXxx()
宏或自己用 (item->type & (1 << cJSON_String))
的位运算来判断类型,或者把位掩码转成可读字符串输出以便调试。
更多推荐
所有评论(0)