Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better diagnostics #2562

Merged
merged 45 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a4d491e
:construction: better diagnostics
nlohmann Jan 1, 2021
7b04786
:construction: add diagnostics to exceptions
nlohmann Jan 2, 2021
ecaab32
:construction: add switch for diagnostics
nlohmann Jan 2, 2021
c6e7fa2
:construction: fix preprocessor check
nlohmann Jan 2, 2021
09cd4ed
:construction: fix preprocessor check
nlohmann Jan 2, 2021
7323a8e
:construction: add tests
nlohmann Jan 2, 2021
ec0b179
:construction: implement more parent relations
nlohmann Jan 2, 2021
294fa34
:bug: fix bug in move constructor
nlohmann Jan 3, 2021
7cdf34b
Merge branch 'develop' of https://github.com/nlohmann/json into diagn…
nlohmann Jan 8, 2021
ddc3bb1
:ok_hand: remove unnecessary assignment from destructor
nlohmann Jan 8, 2021
0617bd2
:ok_hand: fix operator[]
nlohmann Jan 8, 2021
04a0a07
:ok_hand: fix move constructor and move assignment
nlohmann Jan 8, 2021
e4af1dd
:ok_hand: fix operator[]
nlohmann Jan 8, 2021
d4a91b7
:ok_hand: clean operator[]
nlohmann Jan 8, 2021
43cd5c8
:ok_hand: fix constructor
nlohmann Jan 8, 2021
e160749
:recycle: move diagnostic code in header
nlohmann Jan 9, 2021
a834045
:rotating_light: fix warnings
nlohmann Jan 10, 2021
1d6ba22
:recycle: simplify code
nlohmann Jan 10, 2021
9d0150c
:recycle: simplify code
nlohmann Jan 10, 2021
ff57bdc
:bug: fix invariants
nlohmann Jan 10, 2021
10751d1
Merge branch 'develop' of https://github.com/nlohmann/json into diagn…
nlohmann Jan 14, 2021
b9d3aa4
:recycle: split set_parent function
nlohmann Jan 14, 2021
a776216
:memo: fix comment
nlohmann Jan 14, 2021
0d1fb38
:ok_hand: address comment
nlohmann Jan 14, 2021
f803766
:recycle: add iterator set_parent function
nlohmann Jan 14, 2021
1a467a8
Merge branch 'develop' of https://github.com/nlohmann/json into diagn…
nlohmann Jan 15, 2021
b0d8628
:ok_hand: address comments
nlohmann Jan 15, 2021
7633a21
:green_heart: fix build
nlohmann Jan 15, 2021
e9d6411
:bug: proper JSON Pointer escape in diagnostic messages
nlohmann Jan 16, 2021
aeecc09
:white_check_mark: add tests for diagnostics
nlohmann Jan 16, 2021
e23af74
:rotating_light: fix warnings
nlohmann Jan 17, 2021
65107f7
:green_heart: fix build
nlohmann Jan 17, 2021
5ec0980
:green_heart: fix build
nlohmann Jan 17, 2021
3337968
:white_check_mark: improve coverage
nlohmann Jan 17, 2021
d6ff059
:ok_hand: addressed review comments
nlohmann Jan 20, 2021
51ac600
:white_check_mark: improve coverage
nlohmann Jan 21, 2021
d00ad33
:memo: update documentation
nlohmann Jan 21, 2021
7b7da08
:memo: update documentation
nlohmann Jan 23, 2021
380a613
:bug: fix bug in diagnostics_t
nlohmann Jan 23, 2021
c190a72
:ok_hand: apply suggestion
nlohmann Jan 24, 2021
e8dba10
:white_check_mark: add test
nlohmann Jan 24, 2021
74cc0ab
:recycle: remove diagnostics_t class
nlohmann Jan 25, 2021
524eea5
:ok_hand: remove unused template parameter
nlohmann Jan 26, 2021
4917e7c
Merge branch 'develop' of https://github.com/nlohmann/json into diagn…
nlohmann Feb 7, 2021
56a6dec
:twisted_rightwards_arrows: merge develop branch
nlohmann Feb 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${M
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF)
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
option(JSON_Diagnostics "Enable better diagnostic messages." OFF)

##
## CONFIGURATION
Expand Down Expand Up @@ -63,6 +64,10 @@ if (NOT JSON_ImplicitConversions)
message(STATUS "Implicit conversions are disabled")
endif()

if (JSON_Diagnostics)
message(STATUS "Diagnostics enabled")
endif()

##
## TARGET
## create target and add include path
Expand All @@ -79,6 +84,7 @@ target_compile_definitions(
${NLOHMANN_JSON_TARGET_NAME}
INTERFACE
JSON_USE_IMPLICIT_CONVERSIONS=$<BOOL:${JSON_ImplicitConversions}>
JSON_DIAGNOSTICS=$<BOOL:${JSON_Diagnostics}>
)

target_include_directories(
Expand Down
22 changes: 22 additions & 0 deletions doc/examples/diagnostics_extended.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <iostream>

# define JSON_DIAGNOSTICS 1
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
json j;
j["address"]["street"] = "Fake Street";
j["address"]["housenumber"] = "12";

try
{
int housenumber = j["address"]["housenumber"];
}
catch (json::exception& e)
{
std::cout << e.what() << '\n';
}
}
1 change: 1 addition & 0 deletions doc/examples/diagnostics_extended.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a target="_blank" href="https://wandbox.org/permlink/pbz3ULoJ4maRnV8N"><b>online</b></a>
nlohmann marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions doc/examples/diagnostics_extended.output
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[json.exception.type_error.302] (/address/housenumber) type must be number, but is string
20 changes: 20 additions & 0 deletions doc/examples/diagnostics_standard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
json j;
j["address"]["street"] = "Fake Street";
j["address"]["housenumber"] = "12";

try
{
int housenumber = j["address"]["housenumber"];
}
catch (json::exception& e)
{
std::cout << e.what() << '\n';
}
}
1 change: 1 addition & 0 deletions doc/examples/diagnostics_standard.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a target="_blank" href="https://wandbox.org/permlink/fWfQhHzG03P6PAcC"><b>online</b></a>
1 change: 1 addition & 0 deletions doc/examples/diagnostics_standard.output
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[json.exception.type_error.302] type must be number, but is string
10 changes: 10 additions & 0 deletions doc/mkdocs/docs/features/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ This macro overrides `#!cpp catch` calls inside the library. The argument is the

See [Switch off exceptions](../home/exceptions.md#switch-off-exceptions) for an example.

## `JSON_DIAGNOSTICS`

This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable (default).

When enabled, exception messages contain a [JSON Pointer](json_pointer.md) to the JSON value that triggered the exception, see [Extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) for an example. Note that enabling this macro increases the size of every JSON value by one pointer and adds some runtime overhead.

The diagnostics messages can also be controlled with the CMake option `JSON_Diagnostics` (`OFF` by default) which sets `JSON_DIAGNOSTICS` accordingly.

nlohmann marked this conversation as resolved.
Show resolved Hide resolved
## `JSON_NOEXCEPTION`

Exceptions can be switched off by defining the symbol `JSON_NOEXCEPTION`.
Expand Down Expand Up @@ -56,6 +64,8 @@ When defined to `0`, implicit conversions are switched off. By default, implicit
auto s = j.get<std::string>();
```

Implicit conversions can also be controlled with the CMake option `JSON_ImplicitConversions` (`ON` by default) which sets `JSON_USE_IMPLICIT_CONVERSIONS` accordingly.

## `NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)`

This macro can be used to simplify the serialization/deserialization of types if (1) want to use a JSON object as serialization and (2) want to use the member variable names as object keys in that object.
Expand Down
37 changes: 37 additions & 0 deletions doc/mkdocs/docs/home/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,43 @@ Note that `JSON_THROW_USER` should leave the current scope (e.g., by throwing or
#include <nlohmann/json.hpp>
```

### Extended diagnostic messages

Exceptions in the library are thrown in the local context of the JSON value they are detected. This makes detailed diagnostics messages, and hence debugging, difficult.

??? example

```cpp
--8<-- "examples/diagnostics_standard.cpp"
```

Output:

```
--8<-- "examples/diagnostics_standard.output"
```

This exception can be hard to debug if storing the value `#!c "12"` and accessing it is further apart.

To create better diagnostics messages, each JSON value needs a pointer to its parent value such that a global context (i.e., a path from the root value to the value that lead to the exception) can be created. That global context is provided as [JSON Pointer](../features/json_pointer.md).

As this global context comes at the price of storing one additional pointer per JSON value and runtime overhead to maintain the parent relation, extended diagnostics are disabled by default. They can, however, be enabled by defining the preprocessor symbol [`JSON_DIAGNOSTICS`](../features/macros.md#json_diagnostics) to `1` before including `json.hpp`.

??? example

```cpp
--8<-- "examples/diagnostics_extended.cpp"
```

Output:

```
--8<-- "examples/diagnostics_extended.output"
```

Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.


## Parse errors

This exception is thrown by the library when a parse error occurs. Parse errors
Expand Down
2 changes: 1 addition & 1 deletion doc/mkdocs/docs/home/license.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

The class is licensed under the [MIT License](https://opensource.org/licenses/MIT):

Copyright &copy; 2013-2020 [Niels Lohmann](https://nlohmann.me)
Copyright &copy; 2013-2021 [Niels Lohmann](https://nlohmann.me)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
31 changes: 15 additions & 16 deletions include/nlohmann/detail/conversions/from_json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void from_json(const BasicJsonType& j, typename std::nullptr_t& n)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_null()))
{
JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()), j));
}
n = nullptr;
}
Expand Down Expand Up @@ -58,7 +58,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
}

default:
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j));
}
}

Expand All @@ -67,7 +67,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
{
JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()), j));
}
b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
}
Expand All @@ -77,7 +77,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j));
}
s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
}
Expand All @@ -93,7 +93,7 @@ void from_json(const BasicJsonType& j, ConstructibleStringType& s)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_string()))
{
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j));
}

s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
Expand Down Expand Up @@ -133,7 +133,7 @@ void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
}
l.clear();
std::transform(j.rbegin(), j.rend(),
Expand All @@ -150,7 +150,7 @@ void from_json(const BasicJsonType& j, std::valarray<T>& l)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
}
l.resize(j.size());
std::transform(j.begin(), j.end(), std::begin(l),
Expand Down Expand Up @@ -241,8 +241,7 @@ void())
{
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " +
std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
}

from_json_array_impl(j, arr, priority_tag<3> {});
Expand All @@ -253,7 +252,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_binary()))
{
JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()), j));
}

bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
Expand All @@ -265,7 +264,7 @@ void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
{
if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
{
JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()), j));
}

ConstructibleObjectType ret;
Expand Down Expand Up @@ -319,7 +318,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val)
}

default:
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j));
}
}

Expand Down Expand Up @@ -348,14 +347,14 @@ void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>&
{
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
}
m.clear();
for (const auto& p : j)
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
Expand All @@ -368,14 +367,14 @@ void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyE
{
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j));
}
m.clear();
for (const auto& p : j)
{
if (JSON_HEDLEY_UNLIKELY(!p.is_array()))
{
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
Expand Down
8 changes: 8 additions & 0 deletions include/nlohmann/detail/conversions/to_json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ struct external_constructor<value_t::array>
{
j.m_type = value_t::array;
j.m_value = arr;
j.set_parents();
j.assert_invariant();
}

Expand All @@ -140,6 +141,7 @@ struct external_constructor<value_t::array>
{
j.m_type = value_t::array;
j.m_value = std::move(arr);
j.set_parents();
j.assert_invariant();
}

Expand All @@ -152,6 +154,7 @@ struct external_constructor<value_t::array>
using std::end;
j.m_type = value_t::array;
j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.set_parents();
j.assert_invariant();
}

Expand All @@ -164,6 +167,7 @@ struct external_constructor<value_t::array>
for (const bool x : arr)
{
j.m_value.array->push_back(x);
j.set_parent(j.m_value.array->back());
}
j.assert_invariant();
}
Expand All @@ -179,6 +183,7 @@ struct external_constructor<value_t::array>
{
std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());
}
j.set_parents();
j.assert_invariant();
}
};
Expand All @@ -191,6 +196,7 @@ struct external_constructor<value_t::object>
{
j.m_type = value_t::object;
j.m_value = obj;
j.set_parents();
j.assert_invariant();
}

Expand All @@ -199,6 +205,7 @@ struct external_constructor<value_t::object>
{
j.m_type = value_t::object;
j.m_value = std::move(obj);
j.set_parents();
j.assert_invariant();
}

Expand All @@ -211,6 +218,7 @@ struct external_constructor<value_t::object>

j.m_type = value_t::object;
j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.set_parents();
j.assert_invariant();
}
};
Expand Down
Loading