mirror of
https://github.com/nlohmann/json.git
synced 2026-09-16 13:17:59 +00:00
* docs: document the implicit requirements on basic_json's template parameters The requirements that basic_json places on its eleven template parameters were only implied by how the library uses the resulting object_t, array_t, string_t, etc. Consumers had to discover them by trial and error. Add "Template Parameter Requirements" collecting them, split into what is always required and what is only required when a particular part of the API is instantiated. Notable findings that were previously undocumented: - ObjectType must provide a key_compare member type (actual_object_comparator names object_t::key_compare in both arms of a std::conditional), and its third template parameter is used as a comparator, so std::unordered_map cannot be used without a wrapper. - ArrayType must provide capacity() -- push_back(), emplace_back(), operator+=(), and operator[](size_type) call it unconditionally -- and needs random-access iterators, so std::deque and std::list do not work. - StringType needs contiguous, null-terminated data(), a one-byte value_type, and either assignability from std::to_string or an ADL int_to_string(). - NumberFloatType must be float, double, or long double for parsing and serialization; the integer types must satisfy std::is_integral. - AllocatorType must be stateless, support incomplete types, and use plain pointers. - BooleanType and the number types are union members and must be trivial. Link the new page from the basic_json overview, the types feature page, and the individual type alias pages, and correct the container examples given for ObjectType (std::unordered_map) and ArrayType (std::list), which do not work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix object_comparator_t for object types without key_compare detail::actual_object_comparator selected between object_t::key_compare and default_object_comparator_t with std::conditional. Both type arguments of std::conditional are named eagerly, so object_t::key_compare had to exist regardless of the condition, and the has_key_compare guard added in 3.11.0 never took effect: any ObjectType without a key_compare member type failed to compile while instantiating basic_json itself. Use detected_or_t instead, which resolves through a SFINAE partial specialization and only names object_t::key_compare when it exists. The selected type is unchanged for every object type that compiled before, so object_comparator_t -- a public member type -- keeps its meaning and ABI. has_key_compare had no other users and is removed. Add a regression test using an adapter around std::unordered_map, which has no key_compare; it fails to compile without this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: list the types that are known to work for each template parameter Follow up on the template parameter requirements page: state, for every template parameter, which concrete types work and where they stop working. Each entry was verified by compiling and running a common workload (DOM access, dump, parse, CBOR/MessagePack round-trip, flatten, hash) against that instantiation. Findings worth calling out: - ObjectType no longer needs a key_compare member type, so the std::unordered_map adapter only has to restore the template argument order. A hash-ordered ObjectType works everywhere except unflatten(), which reconstructs an array only when it meets the reference token 0 before the other indices. - ArrayType: std::deque works when wrapped to add capacity(); std::list does not. - StringType: std::pmr::string and std::basic_string with a custom allocator compile for the DOM, dump, and parse, but not for the binary readers, flatten, or diff, because the library assigns std::string values to string_t and int_to_string cannot be overloaded for a type in namespace std. - NumberFloatType: long double works for dump and parse but not for the binary formats, which have no encoding for it. - BinaryType: std::vector<std::byte> supports assignment, get, and the binary formats, but neither dump nor std::hash<basic_json>. Also record the object_comparator_t fix in its version history. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix unflatten and binary dumping for non-default configurations unflatten() decided between array and object by looking at the first reference token it happened to see for a node: it started an array only when that token was 0. With a sorted object type the token 0 always arrives first, so the result was correct by accident; with an object type whose iteration order is unspecified, {"/c/2":3,"/c/1":2,"/c/0":1} unflattened to an object with the keys "0", "1", and "2" instead of an array. Collect the pointer prefixes that have a reference token 0 among their children before building the result, and let get_and_create() consult that set. The outcome is now independent of the iteration order and matches, for every input, what a sorted object type produced before: a value is restored as an array if and only if one of its keys is 0. Iterating the flattened object in a different order would have been simpler, but it would have changed the key order of the result for insertion-ordered object types. The serializer, std::hash, and the UBJSON writer converted the elements of a binary value to an integer implicitly, which does not compile for a BinaryType whose value type is std::byte, and which made dump() write the bytes of a signed value type as negative numbers. Convert to std::uint8_t explicitly in all three places, so every byte type dumps as 0..255. The default std::vector<std::uint8_t> configuration is unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: note which Abseil containers can be used as template arguments Checked against Abseil release 20250127.0 with the same workload as the other entries on the page (DOM access, dump, parse, CBOR/MessagePack/UBJSON round-trip, flatten, hash), with and without JSON_DIAGNOSTICS. absl::flat_hash_map and absl::node_hash_map work as ObjectType through an adapter that restores the template argument order and makes erase(iterator) return the following iterator, which Abseil's returns as void. The page now carries that adapter, and notes that absl::flat_hash_map does not keep references to the mapped values valid across insertions while absl::node_hash_map does. Both have a capacity() member, so JSON_DIAGNOSTICS already refreshes the parent pointers conservatively for them. absl::btree_map and absl::InlinedVector cannot be used at all: object_t and array_t are formed while basic_json is still incomplete, and both inspect their value type at class scope. std::map and std::vector are required by the standard to tolerate this, third-party containers generally are not, so the page states the constraint on its own rather than only per container. absl::InlinedVector does work as BinaryType, where it is instantiated with a complete type. absl::FixedArray and absl::Cord are not usable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Relax the ArrayType and ObjectType requirements Two requirements forced users of otherwise suitable containers to write a wrapper, and neither was load-bearing. array_t::capacity() was read in push_back(), emplace_back(), operator+=(), and operator[](size_type), but set_parent() only looks at the value under JSON_DIAGNOSTICS; without diagnostics it was computed and discarded. Read it through array_capacity(), which reports unknown_size() when diagnostics are off or when the array type has no capacity() at all, and treat an unknown capacity as "the elements may have moved" so the parent pointers are refreshed conservatively. std::deque now works as ArrayType, in both builds, and capacity() is no longer named at all in a default build. Since the capacity is now only meaningful for array insertions, it moves out of set_parent() into set_parent_after_array_insert(). basic_json::erase(iterator) assigned the object's erase() return value, which requires the container to return the following iterator. Abseil's hash maps return void to avoid computing a successor the caller may not need. Detect that and compute the successor before erasing; containers that return an iterator, including the vector-backed ordered_map where a precomputed successor would be wrong, keep the existing path. Together these leave an Abseil hash map needing only an alias that restores the template argument order, and no adapter at all for std::deque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not require string_t to be convertible from std::string Three places built a std::string and handed it to something expecting a string_t: the UBJSON high-precision number reader, which every binary reader instantiates, and the BSON writer's array element size calculation and write. That silently required string_t to be implicitly convertible from std::string, which std::string itself and types with a string_view conversion satisfy, but many string types do not. Construct the string_t explicitly from the data and size, which the requirements already cover. This makes boost::container::string, eastl::string, std::pmr::string, and std::basic_string with a custom allocator work as StringType, none of which could previously be used with any binary format. Add binary format coverage to the alt_string test, which had none, including a UBJSON high-precision number -- the case that goes through the reader path. BSON stays uncovered there: it additionally needs string_t::find(value_type), which alt_string does not provide. Also record which containers from Boost, Abseil, and EASTL work for each template parameter, and correct two claims: std::pmr::string is usable after this change, and tsl::ordered_map is not usable at all, because its iterators expose the mapped value as const while basic_json modifies it in place. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: record compatibility for the common header-only hash maps ankerl::unordered_dense (map and segmented_map), phmap (flat_hash_map and node_hash_map), and robin_hood::unordered_flat_map all work as ObjectType through the same adapter as Abseil's and Boost's hash maps, which only has to restore the template argument order. phmap::btree_map and robin_hood::unordered_node_map do not: like the other btree containers they require a complete value type. Note that none of these hash maps defines key_compare, so every one of them depends on object_comparator_t falling back to default_object_comparator_t. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: record Folly and the remaining vector replacements Folly works, with the caveat that its headers need C++20: folly::fbstring as StringType, folly::fbvector and folly::small_vector as ArrayType, folly::fbvector<std::uint8_t> as BinaryType, and folly::F14NodeMap as ObjectType through the usual argument-order adapter. folly::F14FastMap is the exception and requires a complete value type. For ArrayType, boost::container::devector, boost::container::static_vector (within its fixed capacity), and std::pmr::vector work as well. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: cover fifo_map, gtl, folly::sorted_vector_map, and Qt nlohmann::fifo_map works through the adapter that has always been documented for it, and preserves the insertion order. Restore its mention in the object order page, which was dropped together with the tsl::ordered_map one: unlike ordered_map it keeps a lookup index, so it is the insertion-ordered option without the quadratic cost. gtl::flat_hash_map and folly::sorted_vector_map work as well, the latter through an alias that drops the allocator, whose value type it disagrees on. gtl::btree_map does not, for the same reason as the other btree containers. None of the Qt containers can be used, each for its own reason: QMap has no value_type, QHash iterators yield the mapped value rather than a pair, QList has no max_size(), QByteArray spells empty() as isEmpty(), and QString is UTF-16. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: qualify the std::pmr::string support claim Listing std::pmr::string as fully supported was an overclaim: it was only ever checked with the default memory resource, which is not what PMR is for. basic_json cannot be given an allocator or a memory resource, so a pmr string inside a value always allocates from std::pmr::get_default_resource(), and assigning an arena-backed string into a value silently drops its resource, because polymorphic_allocator does not propagate on copy construction. Passing polymorphic_allocator as AllocatorType does not compile either. Only the process-global set_default_resource() redirects these allocations. Say so, and separate the row from std::basic_string with a custom stateless allocator, which is unaffected. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: remove a duplicated StringType compatibility section The StringType section carried two 'Compatible types' tables and two copies of the reference-implementation tip. The second table was a stale copy from before the binary format string fixes and still listed std::pmr::string and std::basic_string with a custom allocator as unusable, contradicting the corrected table a few lines above it, and it dragged along the old explanation that blamed int_to_string. Drop the stale copy and put the surviving table before the notes, so the 'see below' in the std::pmr::string row points forwards. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: correct the template parameter requirements after independent verification Every claim on the page was re-checked by compiling and running it, including the rows that say a type cannot be used, which were checked to fail for the documented reason and not merely to fail. Twenty-four claims were wrong. The most consequential: the incomplete-type constraint applies to ObjectType only. object_t is instantiated inside the class definition, because it is probed for key_compare; array_t is only named there and is not instantiated until basic_json is complete. So eastl::vector, QList and QVector are not excluded by incomplete types at all -- they simply have no max_size() -- and absl::InlinedVector is excluded for a subtler reason of its own. Further corrections: ObjectType does not need erase(key), which has a fallback, but does need at(key) for UBJSON output; only == and < are used, or == and <=> under C++20, not all six; the documented adapter does not fit ankerl or robin_hood. ArrayType needs no initializer-list insert, and value_type, the (count, value) constructor and swappability are per-function, not always. BinaryType needs a range insert for CBOR indefinite-length byte strings and does not need push_back. StringType needs append(const StringType&) unconditionally, and does not need operator!= or operator== against const char*; empty(), resize(n) and reserve(n) are per-subsystem; int_to_string is needed by diff, items and std::hash rather than by JSON Pointer or flatten. BooleanType must be implicitly convertible from bool, and JSONSerializer's second parameter need not carry a default. std::pmr::string was wrong in the other direction this time: a moved-in string does keep its memory resource, and later growth allocates from it. Only copies land on the default resource. Five requirement violations are not caught at compile time rather than the two the page claimed; they are now listed together up front. Split every compatibility table into what works and what does not, as the reasons in the second half are the useful part. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Reduce the string_t and array_t members the library requires Several members were required only because of how the library happened to be written, not because the functionality needs them. Dropping them widens the set of usable string and array types, and one of them was also a performance problem. string_t: - c_str() is gone. Every call site already knew the length and passed it along, so data() is enough. The one place that did not, the diagnostics path in exceptions.hpp, now builds the token from data() and size(), which also stops it from truncating keys that contain a null byte. - back() is gone; the serializer indexes the last character instead. - find(str, pos), replace(), and substr() are gone. escape() and unescape() rebuilt the string with one replace() per escaped character, which moves the tail every time: escaping a string of n characters that all need escaping cost O(n^2). Both now scan with find_first_of() -- a member the pointer parser already required -- and append whole runs, so the common case is one search and one copy. Escaping 64000 tildes drops from 717 ms to 20 ms; a string with nothing to escape gets faster too (8.4 ms to 5.8 ms), because the scan is still a single memchr per pass. json_pointer::split() takes its reference tokens with the (const char*, size_type) constructor rather than substr(). - json_pointer::to_string() accumulates with concat<string_t> instead of letting concat default to std::string and converting afterwards, so streaming a json_pointer no longer requires string_t to be assignable from a std::string. array_t: - at(size_type) is gone. basic_json::at(size_type) checked the index by calling array_t::at() and translating std::out_of_range, which also required the array type to throw that exact exception. It now compares against size() and uses operator[]. The thrown exception, its message, and the behaviour under JSON_NOEXCEPTION are unchanged. The BSON writer wrote the terminating null byte out of the string's own buffer (size() + 1). It now writes the byte itself, so string_t::data() need not be null-terminated for to_bson(). The tests pin the reduced API: alt_string loses the five dropped members and gains coverage of the escaping paths, and a std::vector whose at() is hidden is used as an ArrayType. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: record the reduced string_t and array_t requirements Drop c_str(), back(), find(str, pos), replace(), and substr() from the StringType requirements and at(size_type) from the ArrayType ones, and note the string assignment the JSON pointer code performs. Streaming a json_pointer no longer needs assignability from a std::string. Add the non-null-terminated data() to the list of violations that are not diagnosed at compile time -- it was described in the StringType section but missing from the summary at the top -- and correct the QString row, which no longer fails for the c_str() it lacks. JSON_CATCH_USER no longer wraps a catch of std::out_of_range: the last one went away with array_t::at(). Describe what the library actually catches. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Use character literals for the signed BinaryType test MSVC rejects char(0xFF) with C4310 (cast truncates constant value), which the Windows workflow treats as an error. The character literals carry the same byte values without a narrowing cast. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not instantiate a hash map with an incomplete basic_json in the tests object_t is probed for key_compare inside the definition of basic_json, so it is instantiated while basic_json is still incomplete. Whether a hash map survives that depends on the standard library: libstdc++ 9 needs the size of the mapped type to instantiate std::unordered_map's node type and rejects the adapter, which broke the GCC 9 builds. The test now derives its no-key_compare object type from std::map -- which does cope -- and shadows the inherited key_compare member type with an entity that is not a type, so the library's probe finds none, exactly as for a hash map. The unflatten() order-independence checks in unit-json_pointer already cover the behaviour that the unordered object type was there for. The limitation is documented for std::unordered_map. Also address two Clang-Tidy findings the earlier commits introduced: erase_from_object() declares its iterator with auto, and at(size_type) checks the type first and then falls through to the return instead of throwing from an else branch. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Keep diagnostic key paths null-terminated Building the token from data() and size() kept an embedded null byte in the key, and since what() hands out a C string, that truncated the whole message rather than just the key: to_bson() on a key containing U+0000 reported "[json.exception.out_of_range.409] (/en" instead of the full explanation. This broke test-bson under JSON_DIAGNOSTICS. Constructing from data() alone stops at the first null byte, which is what c_str() did before, so the message is unchanged -- without requiring string_t to provide c_str(). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not parse the value in the array-at() test JSON_DIAGNOSTIC_POSITIONS adds the byte range of the value to the exception message, which a parsed value has and an in-memory one does not, so the two message checks failed in that configuration. Build the array in memory instead of parsing it; the test is about at(size_type) not needing array_t::at(), and the byte range is beside the point. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Move the custom BinaryType tests into their own translation unit The two sections added to unit-regression2.cpp brought a third full basic_json instantiation into a translation unit that was already large. With Clang on MinGW that pushed the object over the reach of a 32-bit relocation and test-regression2_cpp20.exe failed to link: relocation truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata' unit-regression2.cpp is restored to exactly what it was before, and the coverage moves to unit-custom-binary-type.cpp, next to the object and array type tests it belongs with. The signed value type is now also covered in C++11, where std::byte is not available. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not require the container iterators to be nothrow move constructible iter_impl declared its defaulted move operations noexcept. The exception specification a defaulted function gets implicitly follows from its members, here internal_iterator, which holds the object and array iterators. libstdc++ gives std::deque's iterator a user-provided copy constructor without noexcept before version 11, so the implicit specification is noexcept(false) and does not match the declared one. That deletes the function -- and with g++ 4.8, which predates CWG 1778, it is an error outright: error: function 'iter_impl<basic_json<std::map, std::deque> >::iter_impl( iter_impl&&)' defaulted on its first declaration with an exception-specification that differs from the implicit declaration So std::deque, which this branch documents as a usable array type, could not be used with an older standard library. Leaving the specification to be computed cannot mismatch; iteration_proxy_value already spells out the same condition next door. The default configuration is unaffected: json::iterator, json::const_iterator and ordered_json::iterator stay nothrow move constructible and move assignable, which the test now checks so it cannot regress unnoticed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address two Clang-Tidy findings the custom container tests exposed Both come from instantiating basic_json with containers other than the default ones, and neither shows up with the Clang-Tidy version available outside CI: - insert(const_iterator, basic_json&&) forwards its by-value iterator to the const-reference overload. performance-unnecessary-value-param asks for the copy to be a move; it only fires for an iterator that is not trivially copyable, as std::deque's is not. The NOLINT on the function does not cover it, because the finding is reported where the parameter is used rather than where it is declared. Move it, which is what the check asks for and is a (very small) improvement in its own right. - cppcoreguidelines-use-enum-class rejects the unnamed enum that shadowed the inherited key_compare member type. An enum class would not do, since it declares a type of that name and the probe would find it again; a member function declaration hides the name just as well. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Assert the iterators' exception specification relative to the container The test pinned that nlohmann::json's iterators stay nothrow movable after iter_impl's defaulted move operations lost their declared noexcept. That is not a property of the library, though: the exception specification is now computed from the container iterators, so it holds only for standard library implementations whose iterators are themselves nothrow movable. MSVC's checked iterators before VS2017 are not -- _Iterator_base12 registers the iterator with the container's debug proxy in a copy constructor that carries no noexcept -- so the assertions fail on a Visual Studio 2015 debug build, which is the one debug configuration in the AppVeyor matrix and has no counterpart in the GitHub Actions matrix. Assert what the change actually guarantees instead: the iterators are nothrow movable exactly when the object and array iterators they are built from are. That still pins the default configuration against a silent regression, and it is true whatever the standard library provides. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Detect a void-returning erase() through a named trait erase_from_object() distinguished its two overloads with a decltype of a member call written inline in a default template argument. Every other detection in the library goes through the detector machinery in detected.hpp instead -- has_erase_with_key_type is the same question about the same member function -- and the inline form is the one shape older compilers are least reliable about. Express it the same way: detect_erase_with_iterator plus is_detected_exact, both of which the library already relies on elsewhere. No behaviour changes. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Give the custom container types only the constructors the library uses The three container types in the new tests inherited every constructor of their base with using Base::Base. That asks for more than the test needs: the library builds an object or an array by default construction, by copy or move, and -- when converting between two basic_json types or from an initializer list -- from an iterator range. Declaring those directly makes the requirement visible in the test, and keeps object types out of a corner where a compiler has to declare std::map's whole constructor set for a derived class while basic_json is still incomplete. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Temporarily disable the new custom container tests AppVeyor is the only CI that builds MSVC 2015 and 2017, and it has now rejected three heads of this branch. Its build log is not reachable from where this is being worked on, so the verdict is a single bit and the cause has to be narrowed down by bisection. Everything else stays: the library changes, the reduced alt_string, and the unflatten() tests. If AppVeyor passes with these three translation units disabled, the cause is one of the six basic_json instantiations they add; if it fails, it is in the library. Either way this commit is reverted. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Guard the disabled tests with a macro rather than #if 0 Clang-Tidy's readability-avoid-unconditional-preprocessor-if rejects a literal #if 0. Use a macro that is never defined instead, which the check does not look at. Still temporary, and reverted together with the previous commit. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Re-enable the array and binary container tests AppVeyor passed with all three new translation units disabled, so the library changes, the reduced alt_string, and the unflatten() tests are fine on MSVC 2015 and 2017; the cause is one of the six basic_json instantiations the new tests add. Bring back two of the three. If AppVeyor passes again, the cause is in unit-custom-object-type.cpp, which is the one still disabled; if it fails, it is in one of these two and needs one more split. Still temporary. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Diagnose two silently violated template parameter requirements Both were on the list of requirements that are not caught at compile time and corrupt values rather than failing, and both are a plain size comparison: - A BinaryType whose value_type is wider than one byte, which the readers and writers reinterpret as raw bytes anyway. - A NumberUnsignedType too narrow to hold the absolute value of every NumberIntegerType value, which makes basic_json(INT64_MIN).dump() yield -0 for std::int64_t with std::uint32_t. Neither static_assert rejects a configuration that worked before: both only fire where the result was already wrong. Also add the two comments the review asked for, in write_bson_string() and calc_bson_array_size(), matching the ones their counterparts already carry. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Align the template parameter tables and record what is now diagnosed Every table in the page is reformatted so each column is exactly as wide as its widest cell, which is what the review asked for in a dozen places: the separator rows that ran two dashes long, the stray spaces, and the columns padded well past their content. The row listing six containers that require a complete mapped type is split in two so that one cell no longer sets the width of the whole table. Content changes: NumberUnsignedType is described as any unsigned integer type at least as wide as NumberIntegerType rather than any unsigned integer type; the two requirements that are now static_asserts move out of the list of violations that are not caught at compile time; and the two places that require a non-const operator[] say why data() will not do (std::string has no non-const data() before C++17). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Bisect the other way: only the object container tests The previous head touched only docs/, which AppVeyor's only_commits filter skips, so it produced no build and no status at all -- the pull request looked green without ever having been built on MSVC 2015 or 2017. Swap the guards instead of repeating that step: unit-custom-object-type.cpp is enabled and the array and binary translation units are disabled. AppVeyor already passed with all three disabled, so a failure here pins the cause on no_key_compare_json or void_erase_json, and a pass pins it on the array or binary file. Still temporary. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Split the two object types apart AppVeyor failed with only unit-custom-object-type.cpp enabled and passed with all three new translation units disabled, so the cause is one of the two object types in this file and not the array or binary ones. Guard out void_erase_map and leave no_key_compare_map, which separates the two constructs under suspicion: shadowing the inherited key_compare member type with an entity that is not a type, and hiding the inherited erase with a void-returning overload. A failure here points at the first, a pass at the second. Still temporary. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Build the no-key_compare object type by composition, not inheritance The "object type without key_compare" test failed on AppVeyor's MSVC 2017 jobs (/std:c++17): its no_key_compare_map derived publicly from std::map and shadowed the inherited key_compare type with a same-named member function, relying on ordinary member hiding to make key_compare unreachable as a type for the library's detection trait. MSVC 2017 does not honor that hiding for a typename-qualified lookup performed from outside the class and still resolves key_compare to the base's comparator type, so object_comparator_t incorrectly picked it up instead of falling back to default_object_comparator_t. Wrapping a std::map by composition instead removes the base class entirely, so there is no key_compare to find under any lookup rule, on any compiler. Also drops the now-unneeded JSON_BISECT_CUSTOM_CONTAINER_TESTS guard left over from narrowing this down: the void_erase_map test in the same file was never the cause and is re-enabled unconditionally. Verified locally with clang++ and g++ under C++17 and C++20. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Re-enable the array and binary custom-container tests unit-custom-array-type.cpp and unit-custom-binary-type.cpp were still guarded behind JSON_BISECT_CUSTOM_CONTAINER_TESTS from bisecting the AppVeyor failure fixed in7c39f3227, which was unrelated to either file. The macro was never defined, so none of these tests actually ran in CI. Verified locally with clang++ and g++ under C++17 and C++20 before removing the guards. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix indentation of custom_object_type per astyle The one-line function bodies in the composition-based no_key_compare_map (7c39f3227) do not match the project's Allman brace style, which the ci_test_amalgamation job enforces with astyle. Reformatted with the pinned astyle 3.4.13; no functional change. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * docs: add compiled reference implementations for the container template parameters Each of ObjectType, ArrayType, StringType, and BinaryType now links to a minimal, self-contained header (docs/mkdocs/docs/examples/custom_*_type.hpp) that wraps the corresponding standard container by composition and satisfies every "Always required" member listed on that page. Unlike the prose requirement lists, these are real code: each header has a companion .cpp that instantiates a basic_json specialization with it and is compiled and run by the existing ci_test_examples check (docs/Makefile's check_output_portable), so the reference implementations cannot silently drift from what the library actually requires. The .output files were generated with that same target. StringType's existing pointer to tests/src/unit-alt-string.cpp's alt_string is kept alongside the new header as a more thorough, battle-tested example. Verified locally: astyle (pinned 3.4.13, project .astylerc) on the new files; clang++/g++ under C++11/17/20 for each example against the amalgamated header; `make check_output_portable` in docs/; `mkdocs build --strict` and scripts/check_structure.py for the page itself. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Declare no_key_compare_map's accessors noexcept The GCC C++20 job builds with -Wnoexcept and -Werror, and the standard library takes noexcept(c.begin()) and noexcept(c.end()) in ranges_base.h and range_access.h. Forwarding to std::map without repeating its noexcept made those expressions false, which the warning reports as an error: error: noexcept-expression evaluates to 'false' because of a call to no_key_compare_map<...>::begin() [-Werror=noexcept] note: but ... does not throw; perhaps it should be declared 'noexcept' Give the accessors the exception specification of what they forward to. std::map declares begin, end, cbegin, cend, empty, size, max_size, and clear noexcept, so the wrapper does too. swap is left alone: std::map's is only conditionally noexcept, and nothing asks for it. void_erase_map is unaffected because it still derives from std::map and inherits accessors that already carry the specification. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Write out what the defaulted constructor of no_key_compare_map implied ci_test_gcc builds with -Weffc++, which asks for data to be initialized in a member initialization list; a defaulted default constructor does not do that: error: 'no_key_compare_map<...>::data' should be initialized in the member initialization list [-Werror=effc++] Writing the constructor out satisfies that but drops the exception specification the defaulted one carried, which -Wnoexcept then objects to where the standard library takes noexcept(construct(...)). Declare it the way the defaulted constructor was: noexcept when the wrapped map's default constructor is. This is the cost of composition -- inheritance carried std::map's exception specifications and initialization for free, and forwarding by hand has to restate them. Checked with the repository's own GCC warning set from cmake/gcc_flags.cmake, all 346 flags, at C++11, C++17 and C++20: no diagnostics for this file, nor for the two custom container translation units that were disabled while the MSVC failure was narrowed down and are built again now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Mark no_key_compare_map::swap noexcept Clang-Tidy rejects a swap that is not: error: swap functions should be marked noexcept [cppcoreguidelines-noexcept-swap,performance-noexcept-swap] It was left unmarked on the grounds that std::map::swap is only conditionally noexcept, so an unconditional promise would be wrong for a comparator or allocator that can throw while swapping. Both concerns are met by taking the specification from the wrapped map rather than asserting one: noexcept(noexcept(data.swap(other.data))). Clang-Tidy accepts that, and no NOLINT is needed. Last in the series of specifications that inheritance used to supply and composition has to write out by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018hxZxz8svM54c6ATEvXp5E Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1168 lines
44 KiB
C++
1168 lines
44 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm> // all_of
|
|
#include <cctype> // isdigit
|
|
#include <cerrno> // errno, ERANGE
|
|
#include <cstdlib> // strtoull
|
|
#ifndef JSON_NO_IO
|
|
#include <iosfwd> // ostream
|
|
#endif // JSON_NO_IO
|
|
#include <limits> // max
|
|
#include <numeric> // accumulate
|
|
#include <set> // set
|
|
#include <string> // string
|
|
#include <utility> // move
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/detail/exceptions.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
#include <nlohmann/detail/string_concat.hpp>
|
|
#include <nlohmann/detail/string_escape.hpp>
|
|
#include <nlohmann/detail/value_t.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
|
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/
|
|
template<typename RefStringType>
|
|
class json_pointer
|
|
{
|
|
// allow basic_json to access private members
|
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
|
friend class basic_json;
|
|
|
|
template<typename>
|
|
friend class json_pointer;
|
|
|
|
template<typename T>
|
|
struct string_t_helper
|
|
{
|
|
using type = T;
|
|
};
|
|
|
|
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
|
struct string_t_helper<NLOHMANN_BASIC_JSON_TPL>
|
|
{
|
|
using type = StringType;
|
|
};
|
|
|
|
public:
|
|
// for backwards compatibility accept BasicJsonType
|
|
using string_t = typename string_t_helper<RefStringType>::type;
|
|
|
|
/// @brief create JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/
|
|
explicit json_pointer(const string_t& s = "")
|
|
: reference_tokens(split(s))
|
|
{}
|
|
|
|
/// @brief return a string representation of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
|
string_t to_string() const
|
|
{
|
|
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
|
string_t{},
|
|
[](const string_t& a, const string_t& b)
|
|
{
|
|
return detail::concat<string_t>(a, '/', detail::escape(b));
|
|
});
|
|
}
|
|
|
|
/// @brief return a string representation of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string/
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
|
|
operator string_t() const
|
|
{
|
|
return to_string();
|
|
}
|
|
|
|
#ifndef JSON_NO_IO
|
|
/// @brief write string representation of the JSON pointer to stream
|
|
/// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/
|
|
friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr)
|
|
{
|
|
o << ptr.to_string();
|
|
return o;
|
|
}
|
|
#endif
|
|
|
|
/// @brief append another JSON pointer at the end of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
|
json_pointer& operator/=(const json_pointer& ptr)
|
|
{
|
|
reference_tokens.insert(reference_tokens.end(),
|
|
ptr.reference_tokens.begin(),
|
|
ptr.reference_tokens.end());
|
|
return *this;
|
|
}
|
|
|
|
/// @brief append an unescaped reference token at the end of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
|
json_pointer& operator/=(string_t token)
|
|
{
|
|
push_back(std::move(token));
|
|
return *this;
|
|
}
|
|
|
|
/// @brief append an array index at the end of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
|
json_pointer& operator/=(std::size_t array_idx)
|
|
{
|
|
return *this /= std::to_string(array_idx);
|
|
}
|
|
|
|
/// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
|
friend json_pointer operator/(const json_pointer& lhs,
|
|
const json_pointer& rhs)
|
|
{
|
|
return json_pointer(lhs) /= rhs;
|
|
}
|
|
|
|
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
|
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
|
{
|
|
return json_pointer(lhs) /= std::move(token);
|
|
}
|
|
|
|
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
|
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
|
{
|
|
return json_pointer(lhs) /= array_idx;
|
|
}
|
|
|
|
/// @brief returns the parent of this JSON pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/
|
|
json_pointer parent_pointer() const
|
|
{
|
|
if (empty())
|
|
{
|
|
return *this;
|
|
}
|
|
|
|
json_pointer res = *this;
|
|
res.pop_back();
|
|
return res;
|
|
}
|
|
|
|
/// @brief remove first reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/pop_front/
|
|
void pop_front()
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
reference_tokens.erase(reference_tokens.begin());
|
|
}
|
|
|
|
/// @brief return first reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/front/
|
|
const string_t& front() const
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
return reference_tokens.front();
|
|
}
|
|
|
|
/// @brief append an unescaped token at the start of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_front/
|
|
void push_front(const string_t& token)
|
|
{
|
|
reference_tokens.insert(reference_tokens.begin(), token);
|
|
}
|
|
|
|
/// @brief append an unescaped token at the start of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_front/
|
|
void push_front(string_t&& token)
|
|
{
|
|
reference_tokens.insert(reference_tokens.begin(), std::move(token));
|
|
}
|
|
|
|
/// @brief remove last reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/pop_back/
|
|
void pop_back()
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
reference_tokens.pop_back();
|
|
}
|
|
|
|
/// @brief return last reference token
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/back/
|
|
const string_t& back() const
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
return reference_tokens.back();
|
|
}
|
|
|
|
/// @brief append an unescaped token at the end of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_back/
|
|
void push_back(const string_t& token)
|
|
{
|
|
reference_tokens.push_back(token);
|
|
}
|
|
|
|
/// @brief append an unescaped token at the end of the reference pointer
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/push_back/
|
|
void push_back(string_t&& token)
|
|
{
|
|
reference_tokens.push_back(std::move(token));
|
|
}
|
|
|
|
/// @brief return whether pointer points to the root document
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/empty/
|
|
bool empty() const noexcept
|
|
{
|
|
return reference_tokens.empty();
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@param[in] s reference token to be converted into an array index
|
|
|
|
@return integer representation of @a s
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index begins not with a digit
|
|
@throw out_of_range.404 if string @a s could not be converted to an integer
|
|
@throw out_of_range.410 if an array index exceeds size_type
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static typename BasicJsonType::size_type array_index(const string_t& s)
|
|
{
|
|
using size_type = typename BasicJsonType::size_type;
|
|
|
|
// error condition (cf. RFC 6901, Sect. 4)
|
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))
|
|
{
|
|
JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
|
}
|
|
|
|
// error condition (cf. RFC 6901, Sect. 4)
|
|
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))
|
|
{
|
|
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
|
}
|
|
|
|
const char* p = s.data();
|
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
|
errno = 0; // strtoull doesn't reset errno
|
|
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
|
if (p == p_end // invalid input or empty string
|
|
|| errno == ERANGE // out of range
|
|
|| JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr));
|
|
}
|
|
|
|
// only triggered on special platforms (like 32bit), see also
|
|
// https://github.com/nlohmann/json/pull/2203
|
|
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)())) // NOLINT(runtime/int)
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE
|
|
}
|
|
|
|
return static_cast<size_type>(res);
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
json_pointer top() const
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(empty()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr));
|
|
}
|
|
|
|
json_pointer result = *this;
|
|
result.reference_tokens = {reference_tokens[0]};
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@brief the reference token sequences that denote arrays
|
|
|
|
@ref unflatten collects the pointer prefixes that have a reference token 0
|
|
among their children; @ref get_and_create creates arrays exactly below
|
|
those prefixes and objects everywhere else. Deciding this up front keeps
|
|
the result independent of the order in which the flattened object is
|
|
iterated, which is unspecified for some object types.
|
|
*/
|
|
using array_parents_t = std::set<std::vector<string_t>>;
|
|
|
|
/*!
|
|
@brief create and return a reference to the pointed to value
|
|
|
|
@complexity Linear in the number of reference tokens.
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if array index is not a number
|
|
@throw type_error.313 if value cannot be unflattened
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType& get_and_create(BasicJsonType& j, const array_parents_t& array_parents) const
|
|
{
|
|
auto* result = &j;
|
|
|
|
// the reference tokens that have been consumed so far; used to look up
|
|
// whether the value to be created below is an array or an object
|
|
std::vector<string_t> prefix;
|
|
|
|
// in case no reference tokens exist, return a reference to the JSON value
|
|
// j which will be overwritten by a primitive value
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (result->type())
|
|
{
|
|
case detail::value_t::null:
|
|
{
|
|
if (array_parents.find(prefix) != array_parents.end())
|
|
{
|
|
// some reference token below this position is 0, so the
|
|
// value is an array
|
|
result = &result->operator[](array_index<BasicJsonType>(reference_token));
|
|
}
|
|
else
|
|
{
|
|
// start a new object otherwise
|
|
result = &result->operator[](reference_token);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::object:
|
|
{
|
|
// create an entry in the object
|
|
result = &result->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
// create an entry in the array
|
|
result = &result->operator[](array_index<BasicJsonType>(reference_token));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
The following code is only reached if there exists a reference
|
|
token _and_ the current value is primitive. In this case, we have
|
|
an error situation, because primitive values may only occur as
|
|
a single value; that is, with an empty list of reference tokens.
|
|
*/
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
|
}
|
|
|
|
prefix.push_back(reference_token);
|
|
}
|
|
|
|
return *result;
|
|
}
|
|
|
|
/*!
|
|
@brief return a reference to the pointed to value
|
|
|
|
@note This version does not throw if a value is not present, but tries to
|
|
create nested values instead. For instance, calling this function
|
|
with pointer `"/this/that"` on a null value is equivalent to calling
|
|
`operator[]("this").operator[]("that")` on that value, effectively
|
|
changing the null value to an object.
|
|
|
|
@param[in] ptr a JSON value
|
|
|
|
@return reference to the JSON value pointed to by the JSON pointer
|
|
|
|
@complexity Linear in the length of the JSON pointer.
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
@throw out_of_range.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType& get_unchecked(BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
// convert null values to arrays or objects before continuing
|
|
if (ptr->is_null())
|
|
{
|
|
// check if the reference token is a number
|
|
const bool nums =
|
|
std::all_of(reference_token.begin(), reference_token.end(),
|
|
[](const unsigned char x)
|
|
{
|
|
return std::isdigit(x);
|
|
});
|
|
|
|
// change value to an array for numbers or "-" or to object otherwise
|
|
*ptr = (nums || reference_token == "-")
|
|
? detail::value_t::array
|
|
: detail::value_t::object;
|
|
}
|
|
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// use unchecked object access
|
|
ptr = &ptr->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (reference_token == "-")
|
|
{
|
|
// explicitly treat "-" as index beyond the end
|
|
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
|
}
|
|
else
|
|
{
|
|
// convert array index to number; unchecked access
|
|
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
@throw out_of_range.402 if the array index '-' is used
|
|
@throw out_of_range.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType& get_checked(BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// note: at performs range check
|
|
ptr = &ptr->at(reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
JSON_THROW(detail::out_of_range::create(402, detail::concat(
|
|
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
|
|
") is out of range"), ptr));
|
|
}
|
|
|
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
|
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
|
"array index ", std::to_string(idx), " is out of range"), ptr));
|
|
}
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@brief return a const reference to the pointed to value
|
|
|
|
@param[in] ptr a JSON value
|
|
|
|
@return const reference to the JSON value pointed to by the JSON
|
|
pointer
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
@throw out_of_range.402 if the array index '-' is used
|
|
@throw out_of_range.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// use unchecked object access
|
|
ptr = &ptr->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" cannot be used for const access
|
|
JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
|
}
|
|
|
|
// use unchecked array access
|
|
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
@throw out_of_range.402 if the array index '-' is used
|
|
@throw out_of_range.404 if the JSON pointer can not be resolved
|
|
*/
|
|
template<typename BasicJsonType>
|
|
const BasicJsonType& get_checked(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
// note: at performs range check
|
|
ptr = &ptr->at(reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
JSON_THROW(detail::out_of_range::create(402, detail::concat(
|
|
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
|
|
") is out of range"), ptr));
|
|
}
|
|
|
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
|
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
|
{
|
|
JSON_THROW(detail::out_of_range::create(401, detail::concat(
|
|
"array index ", std::to_string(idx), " is out of range"), ptr));
|
|
}
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr));
|
|
}
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
/*!
|
|
@brief return a pointer to the pointed to value, or `nullptr` if the
|
|
pointer cannot be resolved because a key is missing, an array
|
|
index is out of range, or the array index is "-"
|
|
|
|
@note unlike get_checked(), this never throws for those cases, so it
|
|
can be used to implement a non-throwing fallback (e.g. value())
|
|
that also works when exceptions are disabled
|
|
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
*/
|
|
template<typename BasicJsonType>
|
|
const BasicJsonType* get_checked_or_null(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
const auto it = ptr->find(reference_token);
|
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ptr = &*it;
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
return nullptr;
|
|
}
|
|
|
|
// may throw parse_error.106/109 for a malformed index; an
|
|
// index that is syntactically valid but cannot be
|
|
// represented (out_of_range.404/410) is treated like an
|
|
// out-of-range index below
|
|
typename BasicJsonType::size_type idx{};
|
|
JSON_TRY
|
|
{
|
|
idx = array_index<BasicJsonType>(reference_token);
|
|
}
|
|
JSON_INTERNAL_CATCH (detail::out_of_range&)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*!
|
|
@throw parse_error.106 if an array index begins with '0'
|
|
@throw parse_error.109 if an array index was not a number
|
|
*/
|
|
template<typename BasicJsonType>
|
|
bool contains(const BasicJsonType* ptr) const
|
|
{
|
|
for (const auto& reference_token : reference_tokens)
|
|
{
|
|
switch (ptr->type())
|
|
{
|
|
case detail::value_t::object:
|
|
{
|
|
if (!ptr->contains(reference_token))
|
|
{
|
|
// we did not find the key in the object
|
|
return false;
|
|
}
|
|
|
|
ptr = &ptr->operator[](reference_token);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
|
{
|
|
// "-" always fails the range check
|
|
return false;
|
|
}
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9")))
|
|
{
|
|
// invalid char
|
|
return false;
|
|
}
|
|
if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))
|
|
{
|
|
// the first char should be between '1' and '9'
|
|
return false;
|
|
}
|
|
for (std::size_t i = 1; i < reference_token.size(); i++)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))
|
|
{
|
|
// other char should be between '0' and '9'
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// the reference token consists only of digits at this point (cf. checks
|
|
// above); however, its numeric value might not be representable, in which
|
|
// case array_index() would throw out_of_range.404/410 -- contains() must
|
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
|
errno = 0; // strtoull() does not reset errno on success
|
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
|
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
|
{
|
|
// the array index cannot be represented as size_type
|
|
return false;
|
|
}
|
|
|
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
|
if (idx >= ptr->size())
|
|
{
|
|
// index out of range
|
|
return false;
|
|
}
|
|
|
|
ptr = &ptr->operator[](idx);
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
{
|
|
// we do not expect primitive values if there is still a
|
|
// reference token to process
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// no reference token left means we found a primitive value
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
@brief split the string input to reference tokens
|
|
|
|
@note This function is only called by the json_pointer constructor.
|
|
All exceptions below are documented there.
|
|
|
|
@throw parse_error.107 if the pointer is not empty or begins with '/'
|
|
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
|
*/
|
|
static std::vector<string_t> split(const string_t& reference_string)
|
|
{
|
|
std::vector<string_t> result;
|
|
|
|
// special case: empty reference string -> no reference tokens
|
|
if (reference_string.empty())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// check if a nonempty reference string begins with slash
|
|
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
|
{
|
|
JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
|
}
|
|
|
|
// extract the reference tokens:
|
|
// - slash: position of the last read slash (or end of string)
|
|
// - start: position after the previous slash
|
|
for (
|
|
// search for the first slash after the first character
|
|
std::size_t slash = reference_string.find_first_of('/', 1),
|
|
// set the beginning of the first reference token
|
|
start = 1;
|
|
// we can stop if start == 0 (if slash == string_t::npos)
|
|
start != 0;
|
|
// set the beginning of the next reference token
|
|
// (will eventually be 0 if slash == string_t::npos)
|
|
start = (slash == string_t::npos) ? 0 : slash + 1,
|
|
// find next slash
|
|
slash = reference_string.find_first_of('/', start))
|
|
{
|
|
// use the text between the beginning of the reference token
|
|
// (start) and the last slash (slash).
|
|
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
|
auto reference_token = string_t(reference_string.data() + start, count);
|
|
|
|
// check reference tokens are properly escaped
|
|
for (std::size_t pos = reference_token.find_first_of('~');
|
|
pos != string_t::npos;
|
|
pos = reference_token.find_first_of('~', pos + 1))
|
|
{
|
|
JSON_ASSERT(reference_token[pos] == '~');
|
|
|
|
// ~ must be followed by 0 or 1
|
|
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
|
(reference_token[pos + 1] != '0' &&
|
|
reference_token[pos + 1] != '1')))
|
|
{
|
|
JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
|
}
|
|
}
|
|
|
|
// finally, store the reference token
|
|
detail::unescape(reference_token);
|
|
result.push_back(reference_token);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@param[in] reference_string the reference string to the current value
|
|
@param[in] value the value to consider
|
|
@param[in,out] result the result object to insert values to
|
|
|
|
@note Empty objects or arrays are flattened to `null`.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static void flatten(const string_t& reference_string,
|
|
const BasicJsonType& value,
|
|
BasicJsonType& result)
|
|
{
|
|
switch (value.type())
|
|
{
|
|
case detail::value_t::array:
|
|
{
|
|
if (value.m_data.m_value.array->empty())
|
|
{
|
|
// flatten empty array as null
|
|
result[reference_string] = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// iterate array and use index as a reference string
|
|
for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)
|
|
{
|
|
flatten(detail::concat<string_t>(reference_string, '/', std::to_string(i)),
|
|
value.m_data.m_value.array->operator[](i), result);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::object:
|
|
{
|
|
if (value.m_data.m_value.object->empty())
|
|
{
|
|
// flatten empty object as null
|
|
result[reference_string] = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// iterate object and use keys as reference string
|
|
for (const auto& element : *value.m_data.m_value.object)
|
|
{
|
|
flatten(detail::concat<string_t>(reference_string, '/', detail::escape(element.first)), element.second, result);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case detail::value_t::null:
|
|
case detail::value_t::string:
|
|
case detail::value_t::boolean:
|
|
case detail::value_t::number_integer:
|
|
case detail::value_t::number_unsigned:
|
|
case detail::value_t::number_float:
|
|
case detail::value_t::binary:
|
|
case detail::value_t::discarded:
|
|
default:
|
|
{
|
|
// add a primitive value with its reference string
|
|
result[reference_string] = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@param[in] value flattened JSON
|
|
|
|
@return unflattened JSON
|
|
|
|
@throw parse_error.109 if array index is not a number
|
|
@throw type_error.314 if value is not an object
|
|
@throw type_error.315 if object values are not primitive
|
|
@throw type_error.313 if value cannot be unflattened
|
|
*/
|
|
template<typename BasicJsonType>
|
|
static BasicJsonType
|
|
unflatten(const BasicJsonType& value)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!value.is_object()))
|
|
{
|
|
JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value));
|
|
}
|
|
|
|
BasicJsonType result;
|
|
|
|
// collect the pointer prefixes that have a reference token 0 among
|
|
// their children; the values below them are arrays, all others are
|
|
// objects (see array_parents_t)
|
|
array_parents_t array_parents;
|
|
for (const auto& element : *value.m_data.m_value.object)
|
|
{
|
|
json_pointer ptr(element.first);
|
|
std::vector<string_t> prefix;
|
|
for (auto& reference_token : ptr.reference_tokens)
|
|
{
|
|
if (reference_token == "0")
|
|
{
|
|
array_parents.insert(prefix);
|
|
}
|
|
prefix.push_back(std::move(reference_token));
|
|
}
|
|
}
|
|
|
|
// iterate the JSON object values
|
|
for (const auto& element : *value.m_data.m_value.object)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
|
{
|
|
JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second));
|
|
}
|
|
|
|
// Assign the value to the reference pointed to by JSON pointer. Note
|
|
// that if the JSON pointer is "" (i.e., points to the whole value),
|
|
// function get_and_create returns a reference to the result itself.
|
|
// An assignment will then create a primitive value.
|
|
json_pointer(element.first).get_and_create(result, array_parents) = element.second;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// can't use the conversion operator because of ambiguity
|
|
json_pointer<string_t> convert() const&
|
|
{
|
|
json_pointer<string_t> result;
|
|
result.reference_tokens = reference_tokens;
|
|
return result;
|
|
}
|
|
|
|
json_pointer<string_t> convert()&&
|
|
{
|
|
json_pointer<string_t> result;
|
|
result.reference_tokens = std::move(reference_tokens);
|
|
return result;
|
|
}
|
|
|
|
public:
|
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
/// @brief compares two JSON pointers for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeRhs>
|
|
bool operator==(const json_pointer<RefStringTypeRhs>& rhs) const noexcept
|
|
{
|
|
return reference_tokens == rhs.reference_tokens;
|
|
}
|
|
|
|
/// @brief compares JSON pointer and string for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
|
|
bool operator==(const string_t& rhs) const
|
|
{
|
|
return *this == json_pointer(rhs);
|
|
}
|
|
|
|
/// @brief 3-way compares two JSON pointers
|
|
template<typename RefStringTypeRhs>
|
|
std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept // *NOPAD*
|
|
{
|
|
return reference_tokens <=> rhs.reference_tokens; // *NOPAD*
|
|
}
|
|
#else
|
|
/// @brief compares two JSON pointers for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept;
|
|
|
|
/// @brief compares JSON pointer and string for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeLhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs);
|
|
|
|
/// @brief compares string and JSON pointer for equality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
|
template<typename RefStringTypeRhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator==(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs);
|
|
|
|
/// @brief compares two JSON pointers for inequality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept;
|
|
|
|
/// @brief compares JSON pointer and string for inequality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
|
|
template<typename RefStringTypeLhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs);
|
|
|
|
/// @brief compares string and JSON pointer for inequality
|
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/
|
|
template<typename RefStringTypeRhs, typename StringType>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator!=(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs);
|
|
|
|
/// @brief compares two JSON pointer for less-than
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
// NOLINTNEXTLINE(readability-redundant-declaration)
|
|
friend bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept;
|
|
#endif
|
|
|
|
private:
|
|
/// the reference tokens
|
|
std::vector<string_t> reference_tokens;
|
|
};
|
|
|
|
#if !JSON_HAS_THREE_WAY_COMPARISON
|
|
// functions cannot be defined inside the class due to ODR violations
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept
|
|
{
|
|
return lhs.reference_tokens == rhs.reference_tokens;
|
|
}
|
|
|
|
template<typename RefStringTypeLhs,
|
|
typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
|
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs)
|
|
{
|
|
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
|
}
|
|
|
|
template<typename RefStringTypeRhs,
|
|
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
|
inline bool operator==(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs)
|
|
{
|
|
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
|
}
|
|
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
template<typename RefStringTypeLhs,
|
|
typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
|
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const StringType& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
template<typename RefStringTypeRhs,
|
|
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
|
inline bool operator!=(const StringType& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
|
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
|
const json_pointer<RefStringTypeRhs>& rhs) noexcept
|
|
{
|
|
return lhs.reference_tokens < rhs.reference_tokens;
|
|
}
|
|
#endif
|
|
|
|
NLOHMANN_JSON_NAMESPACE_END
|