Skip to content

Commit

Permalink
Add allow_trailing_commas option
Browse files Browse the repository at this point in the history
Signed-off-by: chirsz-ever <[email protected]>
  • Loading branch information
chirsz-ever committed Jan 19, 2025
1 parent e72046e commit ed1d330
Show file tree
Hide file tree
Showing 9 changed files with 300 additions and 93 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1765,6 +1765,14 @@ This library does not support comments by default. It does so for three reasons:

However, you can pass set parameter `ignore_comments` to true in the `parse` function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace.

### Trailing Commas

Trailing commas in arrays and objects are alse not part of the [JSON specification](https://tools.ietf.org/html/rfc8259), and this library does not support it by default.

Like comments, you can pass set parameter `allow_trailing_commas` to true in the `parse` function to allow trailing commas in arrays and objects. Trailing commas will then be ignored.

Note that this library does not add trailing commas when serializing JSON data. Even when you set `allow_trailing_commas` to true, `{,}` and `[,]` are not valid JSON.

### Order of object keys

By default, the library does not preserve the **insertion order of object elements**. This is standards-compliant, as the [JSON standard](https://tools.ietf.org/html/rfc8259.html) defines objects as "an unordered collection of zero or more name/value pairs".
Expand Down
11 changes: 9 additions & 2 deletions docs/mkdocs/docs/api/basic_json/accept.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
// (1)
template<typename InputType>
static bool accept(InputType&& i,
const bool ignore_comments = false);
const bool ignore_comments = false,
const bool allow_trailing_commas = false);

// (2)
template<typename IteratorType>
static bool accept(IteratorType first, IteratorType last,
const bool ignore_comments = false);
const bool ignore_comments = false,
const bool allow_trailing_commas = false);
```
Checks whether the input is valid JSON.
Expand Down Expand Up @@ -50,6 +52,10 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
(`#!cpp false`); (optional, `#!cpp false` by default)
`allow_trailing_commas` (in)
: whether trailing commas in arrays or objects should be allowed (`#!cpp true`) or yield a parse error
(`#!cpp false`); (optional, `#!cpp false` by default)
`first` (in)
: iterator to start of character range
Expand Down Expand Up @@ -102,6 +108,7 @@ A UTF-8 byte order mark is silently ignored.
- Added in version 3.0.0.
- Ignoring comments via `ignore_comments` added in version 3.9.0.
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.11.4.
- Added `allow_trailing_commas` in version 3.11.4.
!!! warning "Deprecation"
Expand Down
11 changes: 9 additions & 2 deletions docs/mkdocs/docs/api/basic_json/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ template<typename InputType>
static basic_json parse(InputType&& i,
const parser_callback_t cb = nullptr,
const bool allow_exceptions = true,
const bool ignore_comments = false);
const bool ignore_comments = false,
const bool allow_trailing_commas = false);

// (2)
template<typename IteratorType>
static basic_json parse(IteratorType first, IteratorType last,
const parser_callback_t cb = nullptr,
const bool allow_exceptions = true,
const bool ignore_comments = false);
const bool ignore_comments = false,
const bool allow_trailing_commas = false);
```
1. Deserialize from a compatible input.
Expand Down Expand Up @@ -56,6 +58,10 @@ static basic_json parse(IteratorType first, IteratorType last,
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
(`#!cpp false`); (optional, `#!cpp false` by default)
`allow_trailing_commas` (in)
: whether trailing commas in arrays or objects should be allowed (`#!cpp true`) or yield a parse error
(`#!cpp false`); (optional, `#!cpp false` by default)
`first` (in)
: iterator to start of character range
Expand Down Expand Up @@ -200,6 +206,7 @@ A UTF-8 byte order mark is silently ignored.
- Overload for contiguous containers (1) added in version 2.0.3.
- Ignoring comments via `ignore_comments` added in version 3.9.0.
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.11.4.
- Added `allow_trailing_commas` in version 3.11.4.
!!! warning "Deprecation"
Expand Down
10 changes: 8 additions & 2 deletions docs/mkdocs/docs/api/basic_json/sax_parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ static bool sax_parse(InputType&& i,
SAX* sax,
input_format_t format = input_format_t::json,
const bool strict = true,
const bool ignore_comments = false);
const bool ignore_comments = false,
const bool allow_trailing_commas = false);

// (2)
template<class IteratorType, class SAX>
static bool sax_parse(IteratorType first, IteratorType last,
SAX* sax,
input_format_t format = input_format_t::json,
const bool strict = true,
const bool ignore_comments = false);
const bool ignore_comments = false,
const bool allow_trailing_commas = false);
```
Read from input and generate SAX events
Expand Down Expand Up @@ -65,6 +67,10 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
(`#!cpp false`); (optional, `#!cpp false` by default)
`allow_trailing_commas` (in)
: whether trailing commas in arrays or objects should be allowed (`#!cpp true`) or yield a parse error
(`#!cpp false`); (optional, `#!cpp false` by default)
`first` (in)
: iterator to start of character range
Expand Down
89 changes: 89 additions & 0 deletions docs/mkdocs/docs/features/trailing_commas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Trailing Commas

Like comments, this library does not support trailing commas in arrays and objects *by default*.

You can pass set parameter `allow_trailing_commas` to `#!c true` in the parse function to allow trailing commas in arrays and objects. Trailing commas are ignored during parsing.

Note that this library does not add trailing commas when serializing JSON data. Even when you set `allow_trailing_commas` to true, `{,}` and `[,]` are not valid JSON.

!!! example

Consider the following JSON with trailing commas.

```json
{
"planets": [
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Uranus",
"Neptune",
]
}
```

When calling `parse` without additional argument, a parse error exception is thrown. If `allow_trailing_commas` is set to `#! true`, the trailing commas are ignored during parsing:

```cpp
#include <iostream>
#include "json.hpp"

using json = nlohmann::json;

int main()
{
std::string s = R"(
{
"planets": [
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Uranus",
"Neptune",
]
}
)";
try
{
json j = json::parse(s);
}
catch (json::exception &e)
{
std::cout << e.what() << std::endl;
}
json j = json::parse(s,
/* callback */ nullptr,
/* allow exceptions */ true,
/* ignore_comments */ false,
/* allow_trailing_commas */ true);
std::cout << j.dump(2) << '\n';
}
```

Output:

```
[json.exception.parse_error.101] parse error at line 3, column 9:
syntax error while parsing object key - invalid literal;
last read: '<U+000A> {<U+000A> /'; expected string literal
```

```json
{
"planets": [
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Uranus",
"Neptune"
]
}
```
63 changes: 40 additions & 23 deletions include/nlohmann/detail/input/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ class parser
explicit parser(InputAdapterType&& adapter,
parser_callback_t<BasicJsonType> cb = nullptr,
const bool allow_exceptions_ = true,
const bool skip_comments = false)
const bool ignore_comments = false,
const bool allow_trailing_commas_ = false)
: callback(std::move(cb))
, m_lexer(std::move(adapter), skip_comments)
, m_lexer(std::move(adapter), ignore_comments)
, allow_exceptions(allow_exceptions_)
, allow_trailing_commas(allow_trailing_commas_)
{
// read first token
get_token();
Expand Down Expand Up @@ -384,11 +386,17 @@ class parser
if (states.back()) // array
{
// comma -> next value
// or end of array (allow_trailing_commas = true)
if (get_token() == token_type::value_separator)
{
// parse a new value
get_token();
continue;

// if allow_trailing_commas and last_token is ], we can continue to "closing ]"
if (!(allow_trailing_commas && last_token == token_type::end_array))
{
continue;
}
}

// closing ]
Expand Down Expand Up @@ -417,32 +425,39 @@ class parser
// states.back() is false -> object

// comma -> next value
// or end of object (allow_trailing_commas = true)
if (get_token() == token_type::value_separator)
{
// parse key
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
}
get_token();

if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
// if allow_trailing_commas and last_token is }, we can continue to "closing }"
if (!(allow_trailing_commas && last_token == token_type::end_object))
{
return false;
}
// parse key
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
}

// parse separator (:)
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
}
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
{
return false;
}

// parse values
get_token();
continue;
// parse separator (:)
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
}

// parse values
get_token();
continue;
}
}

// closing }
Expand Down Expand Up @@ -513,6 +528,8 @@ class parser
lexer_t m_lexer;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
const bool allow_trailing_commas = false;
};

} // namespace detail
Expand Down
Loading

0 comments on commit ed1d330

Please sign in to comment.