Skip to content

Commit

Permalink
Merge e8dba10 into b83fe5d
Browse files Browse the repository at this point in the history
  • Loading branch information
nlohmann committed Jan 24, 2021
2 parents b83fe5d + e8dba10 commit da7782d
Show file tree
Hide file tree
Showing 32 changed files with 1,582 additions and 674 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
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
@@ -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
@@ -0,0 +1 @@
<a target="_blank" href="https://wandbox.org/permlink/pbz3ULoJ4maRnV8N"><b>online</b></a>
1 change: 1 addition & 0 deletions doc/examples/diagnostics_extended.output
@@ -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
@@ -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
@@ -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
@@ -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
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.

## `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
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
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
32 changes: 16 additions & 16 deletions include/nlohmann/detail/conversions/from_json.hpp
Expand Up @@ -13,6 +13,7 @@
#include <valarray> // valarray

#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/diagnostics_t.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
Expand All @@ -27,7 +28,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
n = nullptr;
}
Expand Down Expand Up @@ -58,7 +59,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
}

Expand All @@ -67,7 +68,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
}
Expand All @@ -77,7 +78,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
}
Expand All @@ -93,7 +94,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()), detail::diagnostics_t<BasicJsonType>(j)));
}

s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
Expand Down Expand Up @@ -133,7 +134,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
l.clear();
std::transform(j.rbegin(), j.rend(),
Expand All @@ -150,7 +151,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
l.resize(j.size());
std::transform(j.begin(), j.end(), std::begin(l),
Expand Down Expand Up @@ -241,8 +242,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()), detail::diagnostics_t<BasicJsonType>(j)));
}

from_json_array_impl(j, arr, priority_tag<3> {});
Expand All @@ -253,7 +253,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()), detail::diagnostics_t<BasicJsonType>(j)));
}

bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
Expand All @@ -265,7 +265,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()), detail::diagnostics_t<BasicJsonType>(j)));
}

ConstructibleObjectType ret;
Expand Down Expand Up @@ -319,7 +319,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()), detail::diagnostics_t<BasicJsonType>(j)));
}
}

Expand Down Expand Up @@ -348,14 +348,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()), detail::diagnostics_t<BasicJsonType>(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()), detail::diagnostics_t<BasicJsonType>(j)));
}
m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
}
Expand All @@ -368,14 +368,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()), detail::diagnostics_t<BasicJsonType>(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()), detail::diagnostics_t<BasicJsonType>(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
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

0 comments on commit da7782d

Please sign in to comment.