C语言实现极简递归下降JSON parser解析器

JSON字符串解析利用递归下降非常容易实现。本文实现了一个200多行的极简JSON parser解析器,基本功能都具备,一些限制是:字符串字符只支持ASCII编码,数字解析只支持64位的long整形,进一步的功能也比较容易扩展。

JSON语法分析

根据 https://tools.ietf.org/html/rfc7159 ,摘录部分如下:

JSON-text = ws value ws
begin-array     = ws %x5B ws  ; [ left square bracket
begin-object    = ws %x7B ws  ; { left curly bracket
end-array       = ws %x5D ws  ; ] right square bracket
end-object      = ws %x7D ws  ; } right curly bracket
name-separator  = ws %x3A ws  ; : colon
value-separator = ws %x2C ws  ; , comma

ws = *(%x20 /      ; Space
       %x09 /      ; Horizontal tab
       %x0A /      ; Line feed or New line
       %x0D )      ; Carriage return
       
value = false / null / true / object / array / number / string
        false = %x66.61.6c.73.65   ; false
        null  = %x6e.75.6c.6c      ; null
        true  = %x74.72.75.65      ; true

object = begin-object [ member *( value-separator member ) ]
         end-object
         member = string name-separator value

可以先自己构建一些测试JSON字符串在 http://json.parser.online.fr/ 解析,然后再测试自己的程序。

数据结构设计

JSON是可嵌套的结构,解析后的JSON存储自然也是一个嵌套的结构,解析程序主题算法也是一个递归下降,数据结构设计如下:

json root:1个object
object:1或多个<key, value>, 可以用链表管理
array: 1或多个value,可以用链表管理
key:string
value:string | number | array | object

一个嵌套的数据结构设计完毕后,就可以手写递归下降的JSON解析器了。

JSON解析器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define X64_LONG_MAX 9223372036854775807L

typedef enum json_type_e {
    JSON_NUM,
    JSON_STRING,
    JSON_TRUE,
    JSON_FALSE,
    JSON_NULL,
    JSON_OBJ,
    JSON_ARRAY,
} json_type_t;

typedef struct json_value_s json_value_t;
typedef struct json_obj_s json_obj_t;
typedef struct json_array_s json_array_t;

struct json_value_s {
    json_type_t type;
    union {
        json_obj_t *obj;
        json_array_t *array;
        long num;
        char *str;
    } val;
};

struct json_obj_s {
    char *key;
    json_value_t value;
    json_obj_t *next;
};

struct json_array_s {
    json_value_t value;
    json_array_t *next;
};

typedef struct json_ctx_s {
    const char *cur;
} json_ctx_t;

int parse_obj(json_ctx_t *ctx, json_value_t *v);

int parse_value(json_ctx_t *ctx, json_value_t *v);

int is_whitespace(char c) {
    return c == ' ' || c == '\r' || c == '\n' || c == '\t';
}

void skip_whitespace(json_ctx_t *ctx) {
    while (is_whitespace(*ctx->cur)) {
        ctx->cur++;
    }
}

int parse_string(json_ctx_t *ctx, char **out) {
    ctx->cur++;
    const char *begin = ctx->cur;
    size_t num = 0;
    while (*ctx->cur != '"') {
        ctx->cur++;
        num++;
    }
    char *tmp = (char *) malloc(num + 1);
    if (tmp == NULL)
        return -1;
    strncpy(tmp, begin, num);
    tmp[num] = '\0';
    *out = tmp;
    ctx->cur++;
    return 0;
}

// x64 long
int parse_number(json_ctx_t *ctx, json_value_t *v) {
    long n = 0;
    // https://tools.ietf.org/html/rfc7159#page-6
    if (ctx->cur[0] == '0' && ctx->cur[1] != '0' && isdigit(ctx->cur[1]))
        return -1;

    int sign_flag = 1;
    if (*ctx->cur == '-') {
        sign_flag = -1;
        ctx->cur++;
    }

    while (isdigit(*ctx->cur)) {
        if (n > X64_LONG_MAX / 10)
            return -1;
        if (n == X64_LONG_MAX / 10 && *ctx->cur - '0' > X64_LONG_MAX % 10)
            return -1;
        n = n * 10 + (*ctx->cur) - '0';
        ctx->cur++;
    }
    v->type = JSON_NUM;
    v->val.num = n * sign_flag;
    return 0;
}

int parse_array(json_ctx_t *ctx, json_value_t *v) {
    if (*ctx->cur != '[')
        return -1;

    ctx->cur++;
    skip_whitespace(ctx);
    if (*ctx->cur == ']')
        return 0;

    v->type = JSON_ARRAY;
    while (1) {
        json_array_t *a = malloc(sizeof(json_array_t));
        if (a == NULL)
            return -1;
        a->next = v->val.array;
        v->val.array = a;

        if (parse_value(ctx, &a->value) != 0)
            return -1;
        skip_whitespace(ctx);

        if (*ctx->cur == ']') {
            ctx->cur++;
            return 0;
        }

        if (*ctx->cur == ',') {
            ctx->cur++;
        } else {
            return -1;
        }
        skip_whitespace(ctx);
    }
}

int parse_obj(json_ctx_t *ctx, json_value_t *v) {
    if (*ctx->cur != '{')
        return -1;

    ctx->cur++;
    skip_whitespace(ctx);
    if (*ctx->cur == '}')
        return 0;

    v->type = JSON_OBJ;
    while (1) {
        json_obj_t *m = malloc(sizeof(json_obj_t));
        if (m == NULL)
            return -1;
        m->next = v->val.obj;
        v->val.obj = m;

        // key
        if (*ctx->cur == '"') {
            parse_string(ctx, &m->key);
        } else {
            return -1;
        }
        skip_whitespace(ctx);

        // colon
        if (*ctx->cur++ != ':')
            return -1;
        skip_whitespace(ctx);

        // value
        if (parse_value(ctx, &m->value) == -1)
            return -1;

        // next element
        skip_whitespace(ctx);
        if (*ctx->cur == ',') {
            ctx->cur++;
            skip_whitespace(ctx);
        } else if (*ctx->cur == '}') {
            ctx->cur++;
            return 0;
        } else {
            return -1;
        }
    }
}

int parse_string_word(json_ctx_t *ctx, const char *word, json_value_t *v, json_type_t type) {
    while (*word) {
        if (*ctx->cur != *word)
            return -1;
        ctx->cur++;
        word++;
    }
    v->type = type;
    return 0;
}

int parse_value(json_ctx_t *ctx, json_value_t *v) {
    switch (*ctx->cur) {
        case '"':
            v->type = JSON_STRING;
            return parse_string(ctx, &v->val.str);
        case '{':
            v->type = JSON_OBJ;
            v->val.obj = NULL;
            return parse_obj(ctx, v);
        case 't':
            v->type = JSON_TRUE;
            return parse_string_word(ctx, "true", v, JSON_TRUE);
        case 'f':
            v->type = JSON_FALSE;
            return parse_string_word(ctx, "false", v, JSON_FALSE);
        case 'n':
            v->type = JSON_NULL;
            return parse_string_word(ctx, "null", v, JSON_NULL);
        case '[':
            v->type = JSON_ARRAY;
            v->val.array = NULL;
            return parse_array(ctx, v);
        default:
            return parse_number(ctx, v);
    }
}

json_value_t *parse(const char *json) {
    json_ctx_t ctx;
    ctx.cur = json;
    skip_whitespace(&ctx);
    if (*ctx.cur != '{')  // first must an object
        return NULL;
    json_value_t *v = malloc(sizeof(json_value_t));
    if (v == NULL)
        return NULL;
    int ret = parse_value(&ctx, v);
    if (ret != 0) {
        printf("parse ret %d\n", ret);
        //TODO: free memory
    }
    return v;
}

void show(json_value_t *value) {
    switch (value->type) {
        case JSON_STRING:
            printf("[str]%s\n", value->val.str);
            break;
        case JSON_NUM:
            printf("[num]%ld\n", value->val.num);
            break;
        case JSON_OBJ:
            printf("[obj]-----start\n");
            json_obj_t *head = value->val.obj;
            while (head) {
                printf("[key]%s:\t", head->key);
                printf("[value]");
                show(&head->value);
                head = head->next;
            }
            printf("[obj]-----end\n");
            break;
        case JSON_ARRAY:
            printf("[array]-----start\n");
            json_array_t *arr = value->val.array;
            while (arr) {
                printf("[value]");
                show(&arr->value);
                arr = arr->next;
            }
            printf("[array]-----end\n");
            break;
        case JSON_TRUE:
            printf("true\n");
            break;
        case JSON_FALSE:
            printf("false\n");
            break;
        case JSON_NULL:
            printf("null\n");
            break;
        default:
            return;
    }
}

int main() {
    char *json = "{\"hi\":[1,\"hi\",{\"hello\":22}],\n"
                 "\"isNull  \":null,\n"
                 "\"isTrue\":  true,\n"
                 "\"hello2\":-2,\n"
                 "\"arr2\":[\"hi\",3]}";
    json_value_t *value = parse(json);
    if (value == NULL) {
        printf("parse error!\n");
        return -1;
    }
    show(value);
    return 0;
}

后面可以进一步扩展 ?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值