Skip to content

Commit 58f5f25

Browse files
sushshringSush Shringarputale
andauthored
json start/end position implementation (#4517)
* Add implementation to retrieve start and end positions of json during parse * Add more unit tests and add start/stop parsing for arrays * Add raw value for all types * Add more tests and fix compiler warning * Amalgamate * Fix CLang GCC warnings * Fix error in build * Style using astyle 3.1 * Fix whitespace changes * revert * more whitespace reverts * Address PR comments * Fix failing issues * More whitespace reverts * Address remaining PR comments * Address comments * Switch to using custom base class instead of default basic_json * Adding a basic using for a json using the new base class. Also address PR comments and fix CI failures * Address decltype comments * Diagnostic positions macro (#4) Co-authored-by: Sush Shringarputale <[email protected]> * Fix missed include deletion * Add docs and address other PR comments (#5) * Add docs and address other PR comments --------- Co-authored-by: Sush Shringarputale <[email protected]> * Address new PR comments and fix CI tests for documentation * Update documentation based on feedback (#6) --------- Co-authored-by: Sush Shringarputale <[email protected]> * Address std::size_t and other comments * Fix new CI issues * Fix lcov * Improve lcov case with update to handle_diagnostic_positions call for discarded values * Fix indentation of LCOV_EXCL_STOP comments * fix amalgamation astyle issue --------- Co-authored-by: Sush Shringarputale <[email protected]>
1 parent 733c595 commit 58f5f25

20 files changed

+4784
-2026
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ endif()
4040
option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${JSON_BuildTests_INIT})
4141
option(JSON_CI "Enable CI build targets." OFF)
4242
option(JSON_Diagnostics "Use extended diagnostic messages." OFF)
43+
option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF)
4344
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
4445
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
4546
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
@@ -96,6 +97,10 @@ if (JSON_Diagnostics)
9697
message(STATUS "Diagnostics enabled (JSON_DIAGNOSTICS=1)")
9798
endif()
9899

100+
if (JSON_Diagnostic_Positions)
101+
message(STATUS "Diagnostic positions enabled")
102+
endif()
103+
99104
if (NOT JSON_GlobalUDLs)
100105
message(STATUS "User-defined string literals are not put in the global namespace (JSON_USE_GLOBAL_UDLS=0)")
101106
endif()
@@ -123,6 +128,7 @@ target_compile_definitions(
123128
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
124129
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
125130
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
131+
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
126132
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
127133
)
128134

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <iostream>
2+
3+
#define JSON_DIAGNOSTIC_POSITIONS 1
4+
#include <nlohmann/json.hpp>
5+
6+
using json = nlohmann::json;
7+
8+
int main()
9+
{
10+
std::string json_string = R"(
11+
{
12+
"address": {
13+
"street": "Fake Street",
14+
"housenumber": 1
15+
}
16+
}
17+
)";
18+
json j = json::parse(json_string);
19+
20+
std::cout << "Root diagnostic positions: \n";
21+
std::cout << "\tstart_pos: " << j.start_pos() << '\n';
22+
std::cout << "\tend_pos:" << j.end_pos() << "\n";
23+
std::cout << "Original string: \n";
24+
std::cout << "{\n \"address\": {\n \"street\": \"Fake Street\",\n \"housenumber\": 1\n }\n }" << "\n";
25+
std::cout << "Parsed string: \n";
26+
std::cout << json_string.substr(j.start_pos(), j.end_pos() - j.start_pos()) << "\n\n";
27+
28+
std::cout << "address diagnostic positions: \n";
29+
std::cout << "\tstart_pos:" << j["address"].start_pos() << '\n';
30+
std::cout << "\tend_pos:" << j["address"].end_pos() << "\n\n";
31+
std::cout << "Original string: \n";
32+
std::cout << "{ \"street\": \"Fake Street\",\n \"housenumber\": 1\n }" << "\n";
33+
std::cout << "Parsed string: \n";
34+
std::cout << json_string.substr(j["address"].start_pos(), j["address"].end_pos() - j["address"].start_pos()) << "\n\n";
35+
36+
std::cout << "street diagnostic positions: \n";
37+
std::cout << "\tstart_pos:" << j["address"]["street"].start_pos() << '\n';
38+
std::cout << "\tend_pos:" << j["address"]["street"].end_pos() << "\n\n";
39+
std::cout << "Original string: \n";
40+
std::cout << "\"Fake Street\"" << "\n";
41+
std::cout << "Parsed string: \n";
42+
std::cout << json_string.substr(j["address"]["street"].start_pos(), j["address"]["street"].end_pos() - j["address"]["street"].start_pos()) << "\n\n";
43+
44+
std::cout << "housenumber diagnostic positions: \n";
45+
std::cout << "\tstart_pos:" << j["address"]["housenumber"].start_pos() << '\n';
46+
std::cout << "\tend_pos:" << j["address"]["housenumber"].end_pos() << "\n\n";
47+
std::cout << "Original string: \n";
48+
std::cout << "1" << "\n";
49+
std::cout << "Parsed string: \n";
50+
std::cout << json_string.substr(j["address"]["housenumber"].start_pos(), j["address"]["housenumber"].end_pos() - j["address"]["housenumber"].start_pos()) << "\n\n";
51+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Root diagnostic positions:
2+
start_pos: 5
3+
end_pos:109
4+
Original string:
5+
{
6+
"address": {
7+
"street": "Fake Street",
8+
"housenumber": 1
9+
}
10+
}
11+
Parsed string:
12+
{
13+
"address": {
14+
"street": "Fake Street",
15+
"housenumber": 1
16+
}
17+
}
18+
19+
address diagnostic positions:
20+
start_pos:26
21+
end_pos:103
22+
23+
Original string:
24+
{ "street": "Fake Street",
25+
"housenumber": 1
26+
}
27+
Parsed string:
28+
{
29+
"street": "Fake Street",
30+
"housenumber": 1
31+
}
32+
33+
street diagnostic positions:
34+
start_pos:50
35+
end_pos:63
36+
37+
Original string:
38+
"Fake Street"
39+
Parsed string:
40+
"Fake Street"
41+
42+
housenumber diagnostic positions:
43+
start_pos:92
44+
end_pos:93
45+
46+
Original string:
47+
1
48+
Parsed string:
49+
1
50+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# JSON_DIAGNOSTIC_POSITIONS
2+
3+
```cpp
4+
#define JSON_DIAGNOSTIC_POSITIONS /* value */
5+
```
6+
7+
This macro enables position diagnostics for generated JSON objects.
8+
9+
When enabled, two new properties: `start_pos()` and `end_pos()` are added to `nlohmann::basic_json` objects and fields. `start_pos()` returns the start
10+
position of that JSON object/field in the original string the object was parsed from. Likewise, `end_pos()` returns the end position of that JSON
11+
object/field in the original string the object was parsed from.
12+
13+
`start_pos()` returns the first character of a given element in the original JSON string, while `end_pos()` returns the character following the last
14+
character. For objects and arrays, the first and last characters correspond to the opening or closing braces/brackets, respectively. For fields, the first
15+
and last character represent the opening and closing quotes or the first and last character of the field's numerical or predefined value
16+
(true/false/null), respectively.
17+
18+
Given the above, `end_pos() - start_pos()` for an object or field provides the length of the string representation for that object or field, including the
19+
opening or closing braces, brackets, or quotes.
20+
21+
`start_pos()` and `end_pos()` are only set if the JSON object was parsed using `parse()`. For all other cases, `std::string::npos` will be returned.
22+
23+
Note that enabling this macro increases the size of every JSON value by two `std::size_t` fields and adds
24+
slight runtime overhead.
25+
26+
## Default definition
27+
28+
The default value is `0` (position diagnostics are switched off).
29+
30+
```cpp
31+
#define JSON_DIAGNOSTIC_POSITIONS 0
32+
```
33+
34+
When the macro is not defined, the library will define it to its default value.
35+
36+
## Notes
37+
38+
!!! hint "CMake option"
39+
40+
Diagnostic messages can also be controlled with the CMake option
41+
[`JSON_Diagnostic_Positions`](../../integration/cmake.md#json_diagnostic_positions) (`OFF` by default)
42+
which defines `JSON_DIAGNOSTIC_POSITIONS` accordingly.
43+
44+
## Examples
45+
46+
??? example "Example 1: retrieving positions"
47+
48+
```cpp
49+
--8<-- "examples/diagnostic_positions.cpp"
50+
```
51+
52+
Output:
53+
54+
```
55+
--8<-- "examples/diagnostic_positions.output"
56+
```
57+
58+
The output shows the start/end positions of all the objects and fields in the JSON string.
59+
60+
## Version history
61+

docs/mkdocs/docs/integration/cmake.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ Enable CI build targets. The exact targets are used during the several CI steps
135135

136136
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
137137

138+
### `JSON_Diagnostic_Positions`
139+
Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is off by default.
140+
138141
### `JSON_DisableEnumSerialization`
139142

140143
Disable default `enum` serialization by defining the macro

include/nlohmann/detail/abi_macros.hpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
#define JSON_DIAGNOSTICS 0
2727
#endif
2828

29+
#ifndef JSON_DIAGNOSTIC_POSITIONS
30+
#define JSON_DIAGNOSTIC_POSITIONS 0
31+
#endif
32+
2933
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
3034
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
3135
#endif
@@ -36,6 +40,12 @@
3640
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
3741
#endif
3842

43+
#if JSON_DIAGNOSTIC_POSITIONS
44+
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
45+
#else
46+
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
47+
#endif
48+
3949
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
4050
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
4151
#else
@@ -47,14 +57,15 @@
4757
#endif
4858

4959
// Construct the namespace ABI tags component
50-
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
51-
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
52-
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
60+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
61+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
62+
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
5363

5464
#define NLOHMANN_JSON_ABI_TAGS \
5565
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
5666
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
57-
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
67+
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
68+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
5869

5970
// Construct the namespace version component
6071
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \

include/nlohmann/detail/input/binary_reader.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ static inline bool little_endianness(int num = 1) noexcept
6565
/*!
6666
@brief deserialization of CBOR, MessagePack, and UBJSON values
6767
*/
68-
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType>>
68+
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
6969
class binary_reader
7070
{
7171
using number_integer_t = typename BasicJsonType::number_integer_t;

include/nlohmann/detail/input/input_adapters.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ typename container_input_adapter_factory_impl::container_input_adapter_factory<C
462462
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);
463463
}
464464

465+
// specialization for std::string
466+
using string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));
467+
465468
#ifndef JSON_NO_IO
466469
// Special cases with fast paths
467470
inline file_input_adapter input_adapter(std::FILE* file)

0 commit comments

Comments
 (0)