From fed8c8356be4d94ceb55f2880bdb77fd9b55ef1e Mon Sep 17 00:00:00 2001 From: skewb1k Date: Tue, 10 Feb 2026 03:59:11 +0300 Subject: feature: add option to allow comments in decode. --- README.md | 13 +++++++++++++ lua_cjson.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- tests/test.lua | 26 ++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d2094b1..233a094 100644 --- a/README.md +++ b/README.md @@ -262,3 +262,16 @@ cjson.encode(t) -- {"my_array":[]} properly re-encoded as an array ``` [Back to TOC](#table-of-contents) + +decode_allow_comments +-------------------------- +**syntax:** `cjson.decode_allow_comments(enabled)` + +**default:** false + +If enabled, allows JavaScript-style comments in `cjson.decode` input. Comments +are treated as whitespace and may appear anywhere whitespace is valid in JSON. +Supports single-line comments beginning with '//' and block comments enclosed +with '/* ... */'. + +[Back to TOC](#table-of-contents) diff --git a/lua_cjson.c b/lua_cjson.c index 789817d..de77f26 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -89,6 +89,7 @@ #define DEFAULT_ENCODE_NUMBER_PRECISION 14 #define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 1 #define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0 +#define DEFAULT_DECODE_ALLOW_COMMENTS 0 #define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1 #define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0 #define DEFAULT_ENCODE_INDENT NULL @@ -178,6 +179,7 @@ typedef struct { int decode_invalid_numbers; int decode_max_depth; int decode_array_with_array_mt; + int decode_allow_comments; int encode_skip_unsupported_value_types; } json_config_t; @@ -383,6 +385,16 @@ static int json_cfg_decode_array_with_array_mt(lua_State *l) return 1; } +/* Configures whether decoder allows comments */ +static int json_cfg_decode_allow_comments(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + json_enum_option(l, 1, &cfg->decode_allow_comments, NULL, 1); + + return 1; +} + /* Configure how to treat invalid types */ static int json_cfg_encode_skip_unsupported_value_types(lua_State *l) { @@ -515,6 +527,7 @@ static void json_create_config(lua_State *l) cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT; cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT; + cfg->decode_allow_comments = DEFAULT_DECODE_ALLOW_COMMENTS; cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH; cfg->encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES; cfg->encode_indent = DEFAULT_ENCODE_INDENT; @@ -1279,13 +1292,46 @@ static void json_next_token(json_parse_t *json, json_token_t *token) const json_token_type_t *ch2token = json->cfg->ch2token; int ch; - /* Eat whitespace. */ while (1) { - ch = (unsigned char)*(json->ptr); - token->type = ch2token[ch]; - if (token->type != T_WHITESPACE) + /* Eat whitespace. */ + while (1) { + ch = (unsigned char)*(json->ptr); + token->type = ch2token[ch]; + if (token->type != T_WHITESPACE) + break; + json->ptr++; + } + + if (!json->cfg->decode_allow_comments) break; - json->ptr++; + + /* Eat comments. */ + if ((unsigned char)json->ptr[0] != '/' || + ((unsigned char)json->ptr[1] != '/' && + (unsigned char)json->ptr[1] != '*')) { + break; + } + + if (json->ptr[1] == '/') { + /* Handle single-line comment */ + json->ptr += 2; + while (*json->ptr != '\0' && *json->ptr != '\n') + json->ptr++; + } else { + /* Handle multi-line comment */ + json->ptr += 2; + while (1) { + if (*json->ptr == '\0') { + json_set_token_error(token, json, "unclosed multi-line comment"); + return; + } + if (json->ptr[0] == '*' && json->ptr[1] == '/') { + json->ptr += 2; + break; + } + json->ptr++; + } + } } /* Store location of new token. Required when throwing errors @@ -1625,6 +1671,7 @@ static int lua_cjson_new(lua_State *l) { "decode", json_decode }, { "encode_empty_table_as_object", json_cfg_encode_empty_table_as_object }, { "decode_array_with_array_mt", json_cfg_decode_array_with_array_mt }, + { "decode_allow_comments", json_cfg_decode_allow_comments }, { "encode_sparse_array", json_cfg_encode_sparse_array }, { "encode_max_depth", json_cfg_encode_max_depth }, { "decode_max_depth", json_cfg_decode_max_depth }, diff --git a/tests/test.lua b/tests/test.lua index 0513d97..068a9f0 100755 --- a/tests/test.lua +++ b/tests/test.lua @@ -333,6 +333,32 @@ local cjson_tests = { json.decode, { [["\uDB00\uD"]] }, false, { "Expected value but found invalid unicode escape code at character 2" } }, + -- Test comments + { 'Set decode_allow_comments(true)', + json.decode_allow_comments, { true }, true, { true } }, + { "Decode single-line comment", + json.decode, { '{//comment\n}' }, true, { {} } }, + { "Decode single-line comment with windows newline", + json.decode, { '{//comment\r\n}' }, true, { {} } }, + { "Decode single-line comment after string value", + json.decode, { '"test // /* */ string"//comment' }, true, { "test // /* */ string" } }, + { "Decode multi-line comment", + json.decode, { '{/* A multi-line\ncomment*/}' }, true, { {} } }, + { "Decode multi-line comment before colon", + json.decode, { '{"a" /* Comment */: 1}' }, true, { { a = 1 } } }, + { "Decode multi-line comment after colon", + json.decode, { '{"a": /* Comment */ 1}' }, true, { { a = 1 } } }, + { "Decode multiple comments in a row", + json.decode, { '/*first*//*second*/{}' }, true, { {} } }, + { "Decode unclosed multi-line comment [throw error]", + json.decode, { '{}/*Unclosed' }, + false, { "Expected the end but found unclosed multi-line comment at character 13" } }, + { "Decode comment inside number [throw error]", + json.decode, { '{"a":1/*x*/0}' }, + false, { "Expected comma or object end but found T_INTEGER at character 12" } }, + { 'Set decode_allow_comments(false)', + json.decode_allow_comments, { false }, true, { false } }, + -- Test indenting { 'Set encode_indent(" ")', json.encode_indent, { " " }, true, { " " } }, -- cgit v1.2.3-55-g6feb