diff --git a/README.md b/README.md index 15b63bc8..6c19be74 100644 --- a/README.md +++ b/README.md @@ -1574,7 +1574,9 @@ However, you can pass set parameter `ignore_comments` to true in the `parse` fun ### Order of object keys -By default, the library does not preserve the **insertion order of object elements**. This is standards-compliant, as the [JSON standard](https://tools.ietf.org/html/rfc8259.html) defines objects as "an unordered collection of zero or more name/value pairs". If you do want to preserve the insertion order, you can specialize the object type with containers like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)). +By default, the library does not preserve the **insertion order of object elements**. This is standards-compliant, as the [JSON standard](https://tools.ietf.org/html/rfc8259.html) defines objects as "an unordered collection of zero or more name/value pairs". + +If you do want to preserve the insertion order, you can try the type [`nlohmann::ordered_json`](https://github.com/nlohmann/json/issues/2179). Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)). ### Memory Release diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index bbefa421..7c2cf2b1 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -70,6 +70,7 @@ SOFTWARE. #include #include #include +#include /*! @brief namespace for Niels Lohmann diff --git a/include/nlohmann/json_fwd.hpp b/include/nlohmann/json_fwd.hpp index 824e86a1..332227c1 100644 --- a/include/nlohmann/json_fwd.hpp +++ b/include/nlohmann/json_fwd.hpp @@ -60,6 +60,19 @@ uses the standard template types. @since version 1.0.0 */ using json = basic_json<>; + +template +struct ordered_map; + +/*! +@brief ordered JSON class + +This type preserves the insertion order of object keys. + +@since version 3.9.0 +*/ +using ordered_json = basic_json; + } // namespace nlohmann #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ diff --git a/include/nlohmann/ordered_map.hpp b/include/nlohmann/ordered_map.hpp new file mode 100644 index 00000000..2a72fefd --- /dev/null +++ b/include/nlohmann/ordered_map.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include // less +#include // allocator +#include // pair +#include // vector + +namespace nlohmann +{ + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json +template , + class Allocator = std::allocator>> + struct ordered_map : std::vector, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector, Allocator>; + using typename Container::iterator; + using typename Container::size_type; + using typename Container::value_type; + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} + template + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair emplace(key_type&& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return {it, false}; + } + } + Container::emplace_back(key, t); + return {--this->end(), true}; + } + + T& operator[](Key&& key) + { + return emplace(std::move(key), T{}).first->second; + } + + size_type erase(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } +}; + +} // namespace nlohmann diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0324c11b..82c0d484 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2807,6 +2807,19 @@ uses the standard template types. @since version 1.0.0 */ using json = basic_json<>; + +template +struct ordered_map; + +/*! +@brief ordered JSON class + +This type preserves the insertion order of object keys. + +@since version 3.9.0 +*/ +using ordered_json = basic_json; + } // namespace nlohmann #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -15973,6 +15986,79 @@ class serializer // #include +// #include + + +#include // less +#include // allocator +#include // pair +#include // vector + +namespace nlohmann +{ + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json +template , + class Allocator = std::allocator>> + struct ordered_map : std::vector, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector, Allocator>; + using typename Container::iterator; + using typename Container::size_type; + using typename Container::value_type; + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} + template + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair emplace(key_type&& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return {it, false}; + } + } + Container::emplace_back(key, t); + return {--this->end(), true}; + } + + T& operator[](Key&& key) + { + return emplace(std::move(key), T{}).first->second; + } + + size_type erase(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } +}; + +} // namespace nlohmann + /*! @brief namespace for Niels Lohmann @@ -16099,7 +16185,7 @@ class basic_json detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false - ) + ) { return ::nlohmann::detail::parser(std::move(adapter), std::move(cb), allow_exceptions, ignore_comments); @@ -24635,7 +24721,7 @@ template<> inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( is_nothrow_move_constructible::value&& is_nothrow_move_assignable::value -) + ) { j1.swap(j2); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 43af37d5..9146d43b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -124,6 +124,7 @@ set(files src/unit-modifiers.cpp src/unit-msgpack.cpp src/unit-noexcept.cpp + src/unit-ordered_json.cpp src/unit-pointer_access.cpp src/unit-readme.cpp src/unit-reference_access.cpp diff --git a/test/src/unit-ordered_json.cpp b/test/src/unit-ordered_json.cpp new file mode 100644 index 00000000..2918e442 --- /dev/null +++ b/test/src/unit-ordered_json.cpp @@ -0,0 +1,79 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.8.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; +using nlohmann::ordered_json; + + +TEST_CASE("ordered_json") +{ + json j; + ordered_json oj; + + j["element3"] = 3; + j["element1"] = 1; + j["element2"] = 2; + + oj["element3"] = 3; + oj["element1"] = 1; + oj["element2"] = 2; + + CHECK(j.dump() == "{\"element1\":1,\"element2\":2,\"element3\":3}"); + CHECK(oj.dump() == "{\"element3\":3,\"element1\":1,\"element2\":2}"); + + CHECK(j == json(oj)); + CHECK(ordered_json(json(oj)) == ordered_json(j)); + + j.erase("element1"); + oj.erase("element1"); + + CHECK(j.dump() == "{\"element2\":2,\"element3\":3}"); + CHECK(oj.dump() == "{\"element3\":3,\"element2\":2}"); + + // remove again and nothing changes + j.erase("element1"); + oj.erase("element1"); + + CHECK(j.dump() == "{\"element2\":2,\"element3\":3}"); + CHECK(oj.dump() == "{\"element3\":3,\"element2\":2}"); + + // There are no dup keys cause constructor calls emplace... + json multi {{"z", 1}, {"m", 2}, {"m", 3}, {"y", 4}, {"m", 5}}; + CHECK(multi.size() == 3); + CHECK(multi.dump() == "{\"m\":2,\"y\":4,\"z\":1}"); + + ordered_json multi_ordered {{"z", 1}, {"m", 2}, {"m", 3}, {"y", 4}, {"m", 5}}; + CHECK(multi_ordered.size() == 3); + CHECK(multi_ordered.dump() == "{\"z\":1,\"m\":2,\"y\":4}"); + CHECK(multi_ordered.erase("m") == 1); + CHECK(multi_ordered.dump() == "{\"z\":1,\"y\":4}"); +}