在8位单片机上实现JSON数据的简单解析(单片机解析JSON)

本文介绍了一种在资源受限的单片机上实现JSON解析的方法,包括字符串操作函数及JSON校验、键值获取等核心功能。


由于在一些低配的单片机上面不能直接使用C提供的系统库,项目中又需要解析服务器返回的JSON数据,以下代码可以简单的解析出JSON数据中的key:



//
//  main.c
//  Test
//



/***************************************
 
         一些系统函数的简单实现
 
 ***************************************/

int my_strlen(char *string)
{
    int len = 0;
    while (*string != '\0') {
        string++;
        len++;
    }
    return len;
}

const char *my_strstr(const char *str, const char *sub_str)
{
    for(int i = 0; str[i] != '\0'; i++)
    {
        int tem = i; //tem保留主串中的起始判断下标位置
        int j = 0;
        while(str[i++] == sub_str[j++])
        {
            if(sub_str[j] == '\0')
            {
                return &str[tem];
            }
        }
        i = tem;
    }
    
    return "";
}


char* my_strncpy(char* dest, const char* src, int len)
{
    
    char* temp=dest;
    int i=0;
    while(i++ < len  && (*temp++ = *src++)!='\0')
    {
        
    }
    if(*(temp)!='\0')     *temp='\0';
    return dest;
    
}

int my_strcmp(char* str1, char* str2){
   while(*str1 && *str2 && *str1==*str2)
   {
    ++str1;
     ++str2;
   }
   return *str1-*str2;
}


/***************************************
 
           JSON相关函数声明
 
***************************************/

char json_check(char *str); //JSON 校验函数
char json_get_value(char *json,char *json_key , char *json_value); //JSON获取键值
char json_check_value(char *str1, char *str2); //JSON 键值对比函数




/***************************************
 name:       JSON 校验函数
 input:      字符串
 output:     合法JAON 返回1 不合法JSON 返回0
 description:
 ***************************************/

char json_check(char *str)
{
    char str_length; //定义字符长度变量
    str_length = my_strlen(str); //计算字符长度
    if(str[0] == '{' && str[str_length-1] == '}') //通过首尾大括号判断是否为JSON
    {
        return 1; //如果字符串为合法JSON 返回1
    }
    else
    {
        return 0; //如果字符串为合法JSON 返回0
    }
}



/***************************************
 name:       JSON 获取键值函数
 input:      JSON字符串 要获取的键名 获取键值的字符串
 output:     如果获取成功返回1 获取失败返回0
 description:
 ***************************************/

char json_get_value(char *json,char *json_key , char *json_value)
{
    
    char *json_key_start; //定义键名开始的位置
    char *json_key_end; //定义键名结束的位置
    char json_key_length; //定义键名长度
    
    char *json_value_start; //定义键值开始的位置
    char *json_value_end; //定义键值结束的位置
    char json_value_length; //定义键值长度
    
    json_key_start = my_strstr(json,json_key); //获取键名开始的位置
    json_key_length = my_strlen(json_key); //获取键名的长度
    
    
    json_key_end = json_key_start + json_key_length;  //获取键名结束的位置
    
    
    if(json_key_start != 0 && *(json_key_start - 1) == '\"' && *(json_key_end) == '\"' && *(json_key_end + 1) == ':' && *(json_key_end + 2) == '\"' )
        
    {
        json_value_start = json_key_end + 3; //获取键值开始的位置
        json_value_end = my_strstr(json_value_start,"\""); //获取键值结束的位置
        json_value_length = json_value_end - json_value_start; //获取键值的长度
        
        my_strncpy(json_value,json_value_start,json_value_length); //将键值存入指针数组
        
        json_value[json_value_length] = '\0'; //指针最后一位补字符串结束符 \0
        
        return 1; //成功获取键值 返回1
    }
    else
    {
        json_value[0] = '\0';
        return 0; //失败获取键值 返回0
    }
}



/***************************************
 name:       JSON 键值对比函数
 input:      JSON 键值 要匹配的字符
 output:     如果匹配成功返回1 失败返回0
 description:
 ***************************************/

char json_check_value(char *str1, char *str2)
{
    if(my_strcmp(str1,str2) == 0)
    {
        return 1; //匹配成功返回1
    }
    else
    {
        return 0;//匹配成功返回0
    }
}




/***************************************
 
            测试方法
 
 ***************************************/

int main(int argc, const char * argv[]) {
    
    
    
//    char *json = "{\"test_key\":\"value1\"}";
//    char rs = json_check(json);
//    if(rs == 1){
//        printf("json is valid....\r\n");
//    }else{
//        printf("json is invalid....\r\n");
//    }
//    
//    unsigned char value[20];
//    rs =  json_get_value(json,"test_key",&value);
//    if(rs == 1){
//        printf("parser test_key success....%s  \r\n",value);
//    }else{
//        printf("parser test_key fail....\r\n");
//    }
    
    
    
    char *json = "{\"activecode\":\"phonedirect\",\"uuid\":\"058daba0-f32c-11e6-9055-01160c7a67b1\",\"token\":\"5650ea47fc15d1c19132152681714094fd82fd3d\"}";
    char rs = json_check(json);
    if(rs == 1){
       printf("json is valid....\r\n");
    }else{
        printf("json is invalid....\r\n");
    }
    
    unsigned char value[127];
    rs =  json_get_value(json,"token",&value);
    if(rs == 1){
        printf("parser token success....%s  \r\n",value);
    }else{
        printf("parser token fail....\r\n");
    }
    
    rs =  json_get_value(json,"uuid",&value);
    if(rs == 1){
        printf("parser uuid success....%s  \r\n",value);
    }else{
        printf("parser uuid fail....\r\n");
    }

    
    
    
//    {
//        "key1": "value1",
//        "key2": {
//            "key3": "value3"
//        }
//    }
    
//    char *json = "\"{\"key1\":\"value1\",\"key2\":{\"key3\":\"value3\"}}";
//    char rs = json_check(json);
//    if(rs == 1){
//        printf("json is valid....\r\n");
//    }else{
//        printf("json is invalid....\r\n");
//    }
//
//    unsigned char value[127];
//    rs =  json_get_value(json,"key1",&value);
//    if(rs == 1){
//        printf("parser key1 success....%s  \r\n",value);
//    }else{
//        printf("parser key1 fail....\r\n");
//    }
//    
//    rs =  json_get_value(json,"key2",&value);
//    if(rs == 1){
//        printf("parser key2 success....%s  \r\n",value);
//    }else{
//        printf("parser key2 fail....\r\n");
//    }
//    
//    rs =  json_get_value(json,"key3",&value);
//    if(rs == 1){
//        printf("parser key3 success....%s  \r\n",value);
//    }else{
//        printf("parser key3 fail....\r\n");
//    }
    return 0;
}





<think>我们在8单片机解析JSON数据,由于资源限制(内存和计算能力),通常不能使用标准的解析库(如cJSON)。因此,我们需要采用一些轻量级的方法。 思路: 1. 由于8单片机资源有限,我们可能无法构建完整的DOM树(即完整解析整个JSON对象并存储在内存中)。 2. 我们可以使用基于事件的方式(类似于SAX解析器)来解析JSON,即逐个字符读取并处理,遇到特定结构(如键、值)时触发事件。 3. 或者,我们也可以使用流式解析,只提取我们关心的字段,忽略其他字段。 步骤: 我们可以编写一个简单JSON解析器,它只解析我们需要的字段,并且不保存整个JSON结构。这样,我们可以边接收数据解析,节省内存。 示例代码思路(伪代码): 假设我们需要从一个JSON字符串中提取某个字段(例如"temperature"),我们可以这样: 1. 定义一个状态机,状态包括:寻找键(key)、匹配键、寻找值(value)、解析值(可能是字符串、数字等)。 2. 逐个字符读取输入,根据当前状态和当前字符转换状态。 3. 当匹配到我们需要的键时,我们就开始解析其后的值。 注意:由于8单片机的栈空间有限,递归解析不合适,因此我们使用迭代方式。 下面是一个简单的示例,用于解析一个字符串类型的值(例如:{"key":"value"},我们想要提取键为"key"的值): 状态定义: - STATE_START: 开始,等待'{' - STATE_IN_OBJECT: 在对象中,等待键的开始(引号)或者结束(}) - STATE_IN_KEY: 在键的字符串中 - STATE_AFTER_KEY: 键结束后,等待冒号 - STATE_AFTER_COLON: 冒号后,等待值的开始(可能是字符串、数字、对象等,这里我们只处理字符串) - STATE_IN_VALUE_STRING: 在值字符串中 - ... (其他状态根据需要添加) 具体代码(简化版,只处理字符串值): 注意:这个解析器是非常基础的,没有处理转义字符等复杂情况,实际使用中需要根据需求完善。 然而,由于8单片机资源有限,我们可能还需要考虑: - 避免动态内存分配:使用固定大小的缓冲区来存储键和值(如果键或值过长,则截断或报错)。 - 只解析关心的字段:忽略其他字段,减少处理时间。 下面是一个C语言实现简单示例: ```c #include <stdio.h> #include <string.h> // 解析器状态 typedef enum { STATE_START, // 开始,等待{ STATE_IN_OBJECT, // 在对象中,等待"或} STATE_IN_KEY, // 在键中 STATE_AFTER_KEY, // 键结束,等待: STATE_AFTER_COLON, // 冒号后,等待值开始 STATE_IN_VALUE_STRING, // 在字符串值中 STATE_AFTER_VALUE, // 值结束,等待,或} } ParserState; // 解析上下文 typedef struct { ParserState state; char target_key[32]; // 我们要查找的键 char found_value[64]; // 存储找到的值 char current_key[32]; // 当前解析的键 int key_index; // 当前键的索引 int value_index; // 当前值的索引 int in_quotes; // 是否在引号内(用于处理字符串) int is_escaped; // 是否遇到转义字符 int match_depth; // 匹配深度(如果嵌套对象,我们只关心顶层的键?) } JsonParser; void parser_init(JsonParser *parser, const char *target_key) { memset(parser, 0, sizeof(JsonParser)); strncpy(parser->target_key, target_key, sizeof(parser->target_key)-1); parser->state = STATE_START; } // 处理一个字符 void parser_parse_char(JsonParser *parser, char c) { switch (parser->state) { case STATE_START: if (c == '{') { parser->state = STATE_IN_OBJECT; } break; case STATE_IN_OBJECT: if (c == '"') { // 开始一个键 parser->state = STATE_IN_KEY; parser->key_index = 0; parser->in_quotes = 1; } else if (c == '}') { // 对象结束 parser->state = STATE_START; } break; case STATE_IN_KEY: if (c == '\\') { // 转义字符,这里简单处理,跳过下一个字符(本例中我们不考虑转义,因为我们只匹配固定的键,且不包含特殊字符) // 但为了状态机完整,我们标记转义 parser->is_escaped = 1; } else if (c == '"' && !parser->is_escaped) { // 键结束 parser->state = STATE_AFTER_KEY; parser->in_quotes = 0; // 如果当前键匹配目标键,则准备记录值 if (strcmp(parser->current_key, parser->target_key) == 0) { // 初始化值索引 parser->value_index = 0; } } else { if (parser->key_index < sizeof(parser->current_key)-1) { parser->current_key[parser->key_index++] = c; parser->current_key[parser->key_index] = '\0'; } parser->is_escaped = 0; // 重置转义标记(如果之前被设置,但在非转义情况下这里重置也无妨) } break; case STATE_AFTER_KEY: if (c == ':') { parser->state = STATE_AFTER_COLON; } break; case STATE_AFTER_COLON: if (c == '"') { parser->state = STATE_IN_VALUE_STRING; parser->in_quotes = 1; } // 这里可以扩展数字等其他类型 break; case STATE_IN_VALUE_STRING: if (c == '\\') { parser->is_escaped = 1; } else if (c == '"' && !parser->is_escaped) { // 字符串值结束 parser->state = STATE_AFTER_VALUE; parser->in_quotes = 0; // 如果是在记录值,则结束,这里我们假设我们只关心第一个匹配的键 if (parser->value_index > 0) { parser->found_value[parser->value_index] = '\0'; } } else { // 如果当前键匹配,则记录值 if (strcmp(parser->current_key, parser->target_key) == 0) { if (parser->value_index < sizeof(parser->found_value)-1) { parser->found_value[parser->value_index++] = c; } } parser->is_escaped = 0; } break; case STATE_AFTER_VALUE: // 等待逗号(下一个键值对)或}(对象结束) if (c == ',') { parser->state = STATE_IN_OBJECT; // 回到对象中,等待下一个键 // 清空当前键 parser->current_key[0] = '\0'; parser->key_index = 0; } else if (c == '}') { parser->state = STATE_IN_OBJECT; // 或者回到开始?这里应该是对象结束,可以回到STATE_START,但示例中我们回到IN_OBJECT等待结束 } break; } } int main() { const char *json_str = "{\"name\":\"John\", \"age\":30, \"city\":\"New York\"}"; JsonParser parser; parser_init(&parser, "city"); int len = strlen(json_str); for (int i = 0; i < len; i++) { parser_parse_char(&parser, json_str[i]); } printf("The value for 'city' is: %s\n", parser.found_value); // 应该输出 New York return 0; } ``` 注意:以上代码仅作为示例,没有处理所有JSON情况(如嵌套对象、数组、数字、布尔值等),也没有处理转义字符(如\"在字符串中)。实际应用中需要根据需求扩展。 替代方案: 如果JSON非常简单(比如只有一层,且结构固定),我们可以使用字符串查找函数(如strstr)来定键,然后手动提取值。但这种方法不健壮,容易出错。 总结: 对于8单片机,我们推荐使用轻量级的状态机解析器,只解析需要的字段,避免使用大型库。 引用说明: 本文中提到的解析方法参考了SAX解析器的思想[^1]。 </think>在8单片机上不使用cJSON解析JSON数据,可采用以下轻量级方法: ### 一、资源受限环境的解析方案 1. **基于状态机的流式解析** - 使用有限状态机逐字符处理JSON数据,避免一次性加载整个文档 - 状态转移示例: ``` 开始 → 等待键 → 解析键 → 等待值 → 解析值 → 结束 ↑____________↓ ``` - 优势:内存占用仅需 **100-200字节**(含解析状态和临时缓冲区) 2. **选择性字段提取** ```c // 示例:直接从JSON中提取温度值 while(*json_ptr){ if(strncmp(json_ptr, "\"temp\":", 7) == 0){ json_ptr += 7; sscanf(json_ptr, "%f", &temperature); break; } json_ptr++; } ``` 3. **简化数据类型支持** | 数据类型 | 处理方式 | 内存需求 | |----------|---------------------------|----------| | 数值 | `atof()` 或整数解析 | 4-8字节 | | 字符串 | 指针引用(避免拷贝) | 2字节 | | 布尔值 | 标记存储 (0/1) | 1 | ### 二、具体实现步骤 1. **内存优化配置** ```c #define MAX_DEPTH 2 // 最大嵌套层级 #define KEY_BUF_SIZE 16 // 键缓冲区 #define VAL_BUF_SIZE 32 // 值缓冲区 ``` 2. **核心状态机伪代码** ```c typedef enum { EXPECT_KEY, IN_KEY, EXPECT_VAL, IN_VAL } parse_state; void parse_json(uint8_t* json) { parse_state state = EXPECT_KEY; char key[KEY_BUF_SIZE], val[VAL_BUF_SIZE]; uint8_t key_idx = 0, val_idx = 0; while(*json) { switch(state) { case EXPECT_KEY: if(*json == '"') state = IN_KEY; break; case IN_KEY: if(*json == '"') state = EXPECT_VAL; else key[key_idx++] = *json; break; case EXPECT_VAL: if(*json == ':') state = IN_VAL; break; case IN_VAL: if(*json == ',' || *json == '}') { process_data(key, val); // 处理键值对 reset_buffers(); state = EXPECT_KEY; } else val[val_idx++] = *json; } json++; } } ``` ### 三、性能优化技巧 1. **硬件加速** - 使用UART的DMA传输直接填充解析缓冲区 - 启用CRC校验验证数据完整性 2. **数据预处理** ```c // 移除JSON冗余字符(节省30%处理时间) void sanitize_json(char* json) { while(*json) { if(*json == ' ' || *json == '\n') memmove(json, json+1, strlen(json)); else json++; } } ``` 3. **关键字段定优化** - 对固定格式JSON使用偏移量查找: `char* val_ptr = strstr(json, "\"sensor\"");` - 建立字段置索引表(适用于重复查询) ### 四、替代方案对比 | 方法 | RAM占用 | 代码量 | 适用场景 | |---------------------|-----------|----------|---------------------------| | 状态机解析 | 100-200B | 1-2KB | 动态格式数据 | | 直接内存扫描 | <50B | 0.5KB | 固定格式简单JSON | | 第三方轻量库 | 300-500B | 3-5KB | 需要完整支持 | > **系统资源警告**:在8单片机上(如ATmega328P仅2KB RAM),应避免解析超过3层嵌套或总长超过512字节的JSON数据[^1]。对于复杂场景建议服务端预处理JSON为二进制格式。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值