我的头文件:
#ifndef _json_H_
#define _json_H_
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum { JSON_PRIMITIVE, JSON_OBJECT, JSON_ARRAY, JSON_STRING } JsonType;
typedef enum
{
JSON_ERROR_NOMEM = -1, // Not enough tokens were provided
JSON_ERROR_INVAL = -2, // Invalid character inside JSON string
JSON_ERROR_PART = -3, // The string is not a full JSON packet, more bytes expected
JSON_SUCCESS = 0 // Everthing is fine
} JsonError;
/**
* JSON token description.
* @param type type (object, array, string etc.)
* @param start start position in JSON data string
* @param end end position in JSON data string
*/
typedef struct
{
JsonType type;
int start;
int end;
int size;
#ifdef json_PARENT_LINKS
int parent;
#endif
} JsonToken;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string
*/
typedef struct
{
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g parent object or array */
} JsonParser;
/**
* Create JSON parser over an array of tokens
*/
void json_initJsonParser(JsonParser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
* a single JSON object.
*/
JsonError json_parseJson(JsonParser *parser, const char *js, JsonToken *tokens, unsigned int tokenNum);
#endif /* _json_H_ */
我的C源代码:
/**
* @file json.c
* @brief JSON Parser
*/
#include <stdlib.h>
#include "json.h"
/**
* @fn static JsonToken *json_allocJsonToken(JsonParser *parser, JsonToken *tokens, size_t tokenNum)
* @brief Allocates a fresh unused token from the token pull.
* @param parser
* @param tokens
* @param tokenNum
*/
static JsonToken *json_allocJsonToken(JsonParser *parser, JsonToken *tokens, size_t tokenNum)
{
if (parser->toknext >= tokenNum) return NULL;
JsonToken *tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef json_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* @fn static void json_fillToken(JsonToken *token, JsonType type, int start, int end)
* @brief Fills token type and boundaries.
* @param token
* @param type
* @param start
* @param end
*/
static void json_fillToken(JsonToken *token, JsonType type, int start, int end)
{
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* @fn static JsonError json_parsePrimitive(JsonParser *parser, const char *js, JsonToken *tokens, size_t num_tokens)
* @brief Fills next available token with JSON primitive.
* @param parser
* @param js
* @param tokens
* @param num_tokens
*/
static JsonError json_parsePrimitive(JsonParser *parser, const char *js, JsonToken *tokens, size_t num_tokens)
{
JsonToken *token;
int start;
start = parser->pos;
for (; js[parser->pos] != 'q4312078q'; parser->pos++)
{
switch (js[parser->pos])
{
#ifndef json_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t':
case '\r':
case '\n':
case ' ':
case ',':
case ']':
case '}':
goto found;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127)
{
parser->pos = start;
return JSON_ERROR_INVAL;
}
}
#ifdef json_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSON_ERROR_PART;
#endif
found:
token = json_allocJsonToken(parser, tokens, num_tokens);
if (!token)
{
parser->pos = start;
return JSON_ERROR_NOMEM;
}
json_fillToken(token, JSON_PRIMITIVE, start, parser->pos);
#ifdef json_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return JSON_SUCCESS;
}
/**
* @fn static JsonError json_parseString(JsonParser *parser, const char *js, JsonToken *tokens, size_t num_tokens)
* @brief Fills next token with JSON string.
* @param parser
* @param js
* @param tokens
* @param num_tokens
*/
static JsonError json_parseString(JsonParser *parser, const char *js, JsonToken *tokens, size_t num_tokens)
{
JsonToken *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; js[parser->pos] != 'q4312078q'; parser->pos++)
{
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"')
{
token = json_allocJsonToken(parser, tokens, num_tokens);
if (!token)
{
parser->pos = start;
return JSON_ERROR_NOMEM;
}
json_fillToken(token, JSON_STRING, start+1, parser->pos);
#ifdef json_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return JSON_SUCCESS;
}
/* Backslash: Quoted symbol expected */
if (c == '\')
{
parser->pos++;
switch (js[parser->pos])
{
/* Allowed escaped symbols */
case '\"':
case '/':
case '\':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
break;
/* Allows escaped symbol \uXXXX */
case 'u':
/// \todo handle JSON unescaped symbol \uXXXX
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSON_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSON_ERROR_PART;
}
/**
* @fn JsonError json_parseJson(JsonParser *parser, const char *js, JsonToken *tokens, unsigned int num_tokens)
* @brief Parse JSON string and fill tokens.
* @param parser
* @param js
* @param tokens
* @param num_tokens
*/
JsonError json_parseJson(JsonParser *parser, const char *js, JsonToken *tokens, unsigned int num_tokens)
{
JsonError r;
int i;
JsonToken *token;
for (; js[parser->pos] != 'q4312078q'; parser->pos++)
{
char c;
JsonType type;
c = js[parser->pos];
switch (c)
{
case '{':
case '[':
token = json_allocJsonToken(parser, tokens, num_tokens);
if (!token) return JSON_ERROR_NOMEM;
if (parser->toksuper != -1)
{
tokens[parser->toksuper].size++;
#ifdef json_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSON_OBJECT : JSON_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}':
case ']':
type = (c == '}' ? JSON_OBJECT : JSON_ARRAY);
#ifdef json_PARENT_LINKS
if (parser->toknext < 1) return JSON_ERROR_INVAL;
token = &tokens[parser->toknext - 1];
for (;;)
{
if (token->start != -1 && token->end == -1)
{
if (token->type != type) return JSON_ERROR_INVAL;
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) break;
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--)
{
token = &tokens[i];
if (token->start != -1 && token->end == -1)
{
if (token->type != type) return JSON_ERROR_INVAL;
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) return JSON_ERROR_INVAL;
for (; i >= 0; i--)
{
token = &tokens[i];
if (token->start != -1 && token->end == -1)
{
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = json_parseString(parser, js, tokens, num_tokens);
if (r < 0) return r;
if (parser->toksuper != -1) tokens[parser->toksuper].size++;
break;
case '\t':
case '\r':
case '\n':
case ':':
case ',':
case ' ':
break;
#ifdef json_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 't':
case 'f':
case 'n':
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = json_parsePrimitive(parser, js, tokens, num_tokens);
if (r < 0) return r;
if (parser->toksuper != -1) tokens[parser->toksuper].size++;
break;
#ifdef json_STRICT
/* Unexpected char in strict mode */
default:
return JSON_ERROR_INVAL;
#endif
}
}
for (i = parser->toknext - 1; i >= 0; i--)
{
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) return JSON_ERROR_PART;
}
return JSON_SUCCESS;
}
/**
* @fn void json_initJsonParser(JsonParser *parser)
* @brief Creates a new parser based over a given buffer with an array of tokens available.
* @param parser
*/
void json_initJsonParser(JsonParser *parser)
{
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}
#1 楼
您做得很好据我所知,只要输入正确的代码,代码就会产生正确的输出。
函数感觉就像一个连贯的库,具有一致的函数名和参数的逻辑顺序。
您使用了调用方拥有的所有内存管理策略,该策略对C效果很好。
您可以改进的地方
名称
json_initJsonParser()
有点多余。 json_initParser()
应该足够了。我认为
#ifdef json_PARENT_LINKS
不是一个好主意。用户可以在不支持父链接的情况下轻松地编译库,但是使用已定义的json_PARENT_LINKS
编译应用程序,从而导致讨厌的错误。为什么不总是启用支持父链接?如果没有解析器输出,将很难理解。我认为
#ifdef json_STRICT
作为运行时选项而不是编译时选项会更好。如果用户想同时使用两种模式,则没有合理的方法。JSON原语可能是数字
true
,false
或null
。您接受诸如truthy
之类的非法基元。json_allocJsonToken()
不会按照我期望的方式“分配”内存。同样,在对json_allocJsonToken()
的三个调用中,有两个后跟json_fillToken()
,因此您最好将这两个函数结合起来。首先填写伪值是没有意义的。在
JsonParser
结构中,toknext
和toksuper
应该是相同的数据类型。奇怪的是,一个未签名,而另一个已签名。我建议仅对两者都使用int
,或者对ssize_t
都使用。 请随时在头文件中添加一些带有用法示例的慷慨注释。
#2 楼
我认为代码看起来很整洁。不确定是否可以大大提高速度-您通过存储令牌的元数据(开始/结束位置和类型)有效地对字符串进行了令牌化。这减轻了必须复制数据并处理相关的内存分配问题的痛苦。一些注意事项:
您可以考虑将令牌更改为而是存储一个
const char * start
和一个size_t length
,其中start
指向输入中令牌的第一个字符。这样,您就可以处理令牌的数据,而不必传递对原始字符串的引用。您有方法的文档,但实际上并没有文档化参数-它们的用途,作用
对于一般用途,该接口可能太不方便-您几乎需要在解析(或执行旧的
parse - fail - double memory - try again
舞蹈)之前知道期望有多少个令牌,这可能很难跟踪。作为用户,我可能知道我想解析哪种对象,但很难准确地预先知道我需要多少个json标记。goto
通常令人讨厌。尽管您以一种非常简单明了的方式轻松使用它,但是我几乎可以肯定,使用gotValidToken
令牌标志重写代码以终止循环可能会提高可读性。 goto
迫使您在阅读代码时跳来跳去。
评论
\ $ \ begingroup \ $
我认为使用ssize_t并不是一个好主意,因为那样会使库与POSIX绑定。该范围也不是直观的:它必须能够保持的唯一负值是-1(请在链接的问题中查看此答案)。
\ $ \ endgroup \ $
–杰瓦
15年7月16日在18:18