Skip to content

Commit

Permalink
✨ implemented exception-free parser #458 #582
Browse files Browse the repository at this point in the history
You can now pass a boolean "allow_exceptions" to the parse functions. If it is false, no exceptions are thrown in case of a parse error. Instead, parsing is stopped at the first error and a JSON value of type "discarded" (check with is_discarded()) is returned.
  • Loading branch information
nlohmann committed Jul 27, 2017
1 parent 669ebf5 commit 7d51214
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 23 deletions.
93 changes: 74 additions & 19 deletions src/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3071,8 +3071,11 @@ class parser

/// a parser reading from an input adapter
explicit parser(detail::input_adapter_t adapter,
const parser_callback_t cb = nullptr)
: callback(cb), m_lexer(adapter) {}
const parser_callback_t cb = nullptr,
const bool allow_exceptions_ = true)
: callback(cb), m_lexer(adapter),
allow_exceptions(allow_exceptions_)
{}

/*!
@brief public parser interface
Expand All @@ -3092,12 +3095,20 @@ class parser
parse_internal(true, result);
result.assert_invariant();

// in strict mode, input must be completely read
if (strict)
{
get_token();
expect(token_type::end_of_input);
}

// in case of an error, return discarded value
if (errored)
{
result = value_t::discarded;
return;
}

// set top-level value to null if it was discarded by the callback
// function
if (result.is_discarded())
Expand Down Expand Up @@ -3176,7 +3187,10 @@ class parser
while (true)
{
// store key
expect(token_type::value_string);
if (not expect(token_type::value_string))
{
return;
}
const auto key = m_lexer.get_string();

bool keep_tag = false;
Expand All @@ -3195,7 +3209,10 @@ class parser

// parse separator (:)
get_token();
expect(token_type::name_separator);
if (not expect(token_type::name_separator))
{
return;
}

// parse and add value
get_token();
Expand All @@ -3216,7 +3233,10 @@ class parser
}

// closing }
expect(token_type::end_object);
if (not expect(token_type::end_object))
{
return;
}
break;
}

Expand Down Expand Up @@ -3273,7 +3293,10 @@ class parser
}

// closing ]
expect(token_type::end_array);
if (not expect(token_type::end_array))
{
return;
}
break;
}

Expand Down Expand Up @@ -3334,23 +3357,36 @@ class parser
// throw in case of infinity or NAN
if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float)))
{
JSON_THROW(out_of_range::create(406, "number overflow parsing '" +
m_lexer.get_token_string() + "'"));
if (allow_exceptions)
{
JSON_THROW(out_of_range::create(406, "number overflow parsing '" +
m_lexer.get_token_string() + "'"));
}
else
{
expect(token_type::uninitialized);
}
}
break;
}

case token_type::parse_error:
{
// using "uninitialized" to avoid "expected" message
expect(token_type::uninitialized);
if (not expect(token_type::uninitialized))
{
return;
}
break; // LCOV_EXCL_LINE
}

default:
{
// the last token was unexpected; we expected a value
expect(token_type::literal_or_value);
if (not expect(token_type::literal_or_value))
{
return;
}
break; // LCOV_EXCL_LINE
}
}
Expand Down Expand Up @@ -3455,10 +3491,15 @@ class parser
}
}

case token_type::value_float:
{
// reject infinity or NAN
return std::isfinite(m_lexer.get_number_float());
}

case token_type::literal_false:
case token_type::literal_null:
case token_type::literal_true:
case token_type::value_float:
case token_type::value_integer:
case token_type::value_string:
case token_type::value_unsigned:
Expand All @@ -3483,14 +3524,23 @@ class parser
/*!
@throw parse_error.101 if expected token did not occur
*/
void expect(token_type t)
bool expect(token_type t)
{
if (JSON_UNLIKELY(t != last_token))
{
errored = true;
expected = t;
throw_exception();
if (allow_exceptions)
{
throw_exception();
}
else
{
return false;
}
}

return true;
}

[[noreturn]] void throw_exception() const
Expand Down Expand Up @@ -3527,6 +3577,8 @@ class parser
bool errored = false;
/// possible reason for the syntax error
token_type expected = token_type::uninitialized;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
};

///////////////
Expand Down Expand Up @@ -12823,21 +12875,23 @@ class basic_json
@since version 2.0.3 (contiguous containers)
*/
static basic_json parse(detail::input_adapter i,
const parser_callback_t cb = nullptr)
const parser_callback_t cb = nullptr,
const bool allow_exceptions = true)
{
basic_json result;
parser(i, cb).parse(true, result);
parser(i, cb, allow_exceptions).parse(true, result);
return result;
}

/*!
@copydoc basic_json parse(detail::input_adapter, const parser_callback_t)
*/
static basic_json parse(detail::input_adapter& i,
const parser_callback_t cb = nullptr)
const parser_callback_t cb = nullptr,
const bool allow_exceptions = true)
{
basic_json result;
parser(i, cb).parse(true, result);
parser(i, cb, allow_exceptions).parse(true, result);
return result;
}

Expand Down Expand Up @@ -12901,10 +12955,11 @@ class basic_json
std::random_access_iterator_tag,
typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
static basic_json parse(IteratorType first, IteratorType last,
const parser_callback_t cb = nullptr)
const parser_callback_t cb = nullptr,
const bool allow_exceptions = true)
{
basic_json result;
parser(detail::input_adapter(first, last), cb).parse(true, result);
parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result);
return result;
}

Expand Down
25 changes: 22 additions & 3 deletions test/src/unit-class_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,31 @@ json parser_helper(const std::string& s)
{
json j;
json::parser(nlohmann::detail::input_adapter(s)).parse(true, j);

// if this line was reached, no exception ocurred
// -> check if result is the same without exceptions
json j_nothrow;
CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow));
CHECK(j_nothrow == j);

return j;
}

bool accept_helper(const std::string& s)
{
return json::parser(nlohmann::detail::input_adapter(s)).accept(true);
// 1. parse s without exceptions
json j;
CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j));
const bool ok_noexcept = not j.is_discarded();

// 2. accept s
const bool ok_accept = json::parser(nlohmann::detail::input_adapter(s)).accept(true);

// 3. check if both approaches come to the same result
CHECK(ok_noexcept == ok_accept);

// 4. return result
return ok_accept;
}

TEST_CASE("parser class")
Expand Down Expand Up @@ -590,8 +609,8 @@ TEST_CASE("parser class")

SECTION("overflow")
{
// overflows during parsing yield an exception, but is accepted anyway
CHECK(accept_helper("1.18973e+4932"));
// overflows during parsing
CHECK(not accept_helper("1.18973e+4932"));
}

SECTION("invalid numbers")
Expand Down
Loading

0 comments on commit 7d51214

Please sign in to comment.