diff --git a/CMakeLists.txt b/CMakeLists.txt index cc660c242..cc30013b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,56 +1,78 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.0.0) -# define the project -project(nlohmann_json VERSION 2.1.1 LANGUAGES CXX) +## +## PROJECT +## name and version +## +project(nlohmann_json VERSION 2.1.1) +## +## OPTIONS +## option(JSON_BuildTests "Build the unit tests" ON) -# define project variables -set(JSON_TARGET_NAME ${PROJECT_NAME}) -set(JSON_PACKAGE_NAME ${JSON_TARGET_NAME}) -set(JSON_TARGETS_FILENAME "${JSON_PACKAGE_NAME}Targets.cmake") -set(JSON_CONFIG_FILENAME "${JSON_PACKAGE_NAME}Config.cmake") -set(JSON_CONFIGVERSION_FILENAME "${JSON_PACKAGE_NAME}ConfigVersion.cmake") -set(JSON_CONFIG_DESTINATION "cmake") -set(JSON_INCLUDE_DESTINATION "include/nlohmann") +## +## CONFIGURATION +## +set(NLOHMANN_JSON_TARGET_NAME ${PROJECT_NAME}) +set(NLOHMANN_JSON_SOURCE_DIR "src/") +set(NLOHMANN_JSON_CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}") +set(NLOHMANN_JSON_INCLUDE_INSTALL_DIR "include") +set(NLOHMANN_JSON_HEADER_INSTALL_DIR "${NLOHMANN_JSON_INCLUDE_INSTALL_DIR}/nlohmann") +set(NLOHMANN_JSON_TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") +set(NLOHMANN_JSON_CMAKE_CONFIG_TEMPLATE "cmake/config.cmake.in") +set(NLOHMANN_JSON_CMAKE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake_config") +set(NLOHMANN_JSON_CMAKE_VERSION_CONFIG_FILE "${NLOHMANN_JSON_CMAKE_CONFIG_DIR}/${PROJECT_NAME}ConfigVersion.cmake") +set(NLOHMANN_JSON_CMAKE_PROJECT_CONFIG_FILE "${NLOHMANN_JSON_CMAKE_CONFIG_DIR}/${PROJECT_NAME}Config.cmake") -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +## +## TARGET +## create target and add include path +## +add_library(${NLOHMANN_JSON_TARGET_NAME} INTERFACE) -# create and configure the library target -add_library(${JSON_TARGET_NAME} INTERFACE) -target_include_directories(${JSON_TARGET_NAME} INTERFACE - $ - $) - -# create and configure the unit test target +target_include_directories( + ${NLOHMANN_JSON_TARGET_NAME} + INTERFACE $ +) + +## +## TESTS +## create and configure the unit test target +## if(JSON_BuildTests) enable_testing() + include_directories(${NLOHMANN_JSON_SOURCE_DIR}) add_subdirectory(test) endif() -# generate a config and config version file for the package +## +## INSTALL +## install header files, generate and install cmake config files for find_package() +## include(CMakePackageConfigHelpers) -configure_package_config_file("cmake/config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIG_FILENAME}" - INSTALL_DESTINATION ${JSON_CONFIG_DESTINATION} - PATH_VARS JSON_INCLUDE_DESTINATION) -write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIGVERSION_FILENAME}" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - -# export the library target and store build directory in package registry -export(TARGETS ${JSON_TARGET_NAME} - FILE "${CMAKE_CURRENT_BINARY_DIR}/${JSON_TARGETS_FILENAME}") -export(PACKAGE ${JSON_PACKAGE_NAME}) - -# install library target and config files -install(TARGETS ${JSON_TARGET_NAME} - EXPORT ${JSON_PACKAGE_NAME}) -install(FILES "src/json.hpp" - DESTINATION ${JSON_INCLUDE_DESTINATION}) -install(EXPORT ${JSON_PACKAGE_NAME} - FILE ${JSON_TARGETS_FILENAME} - DESTINATION ${JSON_CONFIG_DESTINATION}) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIG_FILENAME}" - "${CMAKE_CURRENT_BINARY_DIR}/${JSON_CONFIGVERSION_FILENAME}" - DESTINATION ${JSON_CONFIG_DESTINATION}) +write_basic_package_version_file( + ${NLOHMANN_JSON_CMAKE_VERSION_CONFIG_FILE} COMPATIBILITY SameMajorVersion +) +configure_package_config_file( + ${NLOHMANN_JSON_CMAKE_CONFIG_TEMPLATE} + ${NLOHMANN_JSON_CMAKE_PROJECT_CONFIG_FILE} + INSTALL_DESTINATION ${NLOHMANN_JSON_CONFIG_INSTALL_DIR} +) +install( + DIRECTORY ${NLOHMANN_JSON_SOURCE_DIR} + DESTINATION ${NLOHMANN_JSON_HEADER_INSTALL_DIR} +) +install( + FILES ${NLOHMANN_JSON_CMAKE_PROJECT_CONFIG_FILE} ${NLOHMANN_JSON_CMAKE_VERSION_CONFIG_FILE} + DESTINATION ${NLOHMANN_JSON_CONFIG_INSTALL_DIR} +) +install( + TARGETS ${NLOHMANN_JSON_TARGET_NAME} + EXPORT ${NLOHMANN_JSON_TARGETS_EXPORT_NAME} + INCLUDES DESTINATION ${NLOHMANN_JSON_INCLUDE_INSTALL_DIR} +) +install( + EXPORT ${NLOHMANN_JSON_TARGETS_EXPORT_NAME} + DESTINATION ${NLOHMANN_JSON_CONFIG_INSTALL_DIR} +) diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in index a1e9488d2..b4fd29d99 100644 --- a/cmake/config.cmake.in +++ b/cmake/config.cmake.in @@ -1,7 +1,3 @@ @PACKAGE_INIT@ -set_and_check(JSON_INCLUDE_DIR "@PACKAGE_JSON_INCLUDE_DESTINATION@") - -cmake_policy(PUSH) -cmake_policy(SET CMP0024 OLD) -include(${CMAKE_CURRENT_LIST_DIR}/@JSON_TARGETS_FILENAME@) -cmake_policy(POP) +include("${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/src/json.hpp b/src/json.hpp index 92e2b06df..bdd0012da 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -575,6 +575,14 @@ struct external_constructor j.m_value = s; j.assert_invariant(); } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } }; template<> @@ -624,6 +632,14 @@ struct external_constructor j.assert_invariant(); } + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + template::value, @@ -662,6 +678,14 @@ struct external_constructor j.assert_invariant(); } + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + template::value, int> = 0> @@ -850,6 +874,12 @@ void to_json(BasicJsonType& j, const CompatibleString& s) external_constructor::construct(j, s); } +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + template::value, int> = 0> void to_json(BasicJsonType& j, FloatType val) noexcept @@ -900,13 +930,25 @@ void to_json(BasicJsonType& j, const CompatibleArrayType& arr) external_constructor::construct(j, arr); } +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t::value, int> = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& arr) +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { - external_constructor::construct(j, arr); + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); } template +struct json_ref +{ + typedef BasicJsonType value_type; + + json_ref(value_type&& value) + : owned_value_(std::move(value)) + , is_rvalue_(true) + { + value_ref_ = &owned_value_; + } + + json_ref(const value_type& value) + : value_ref_(const_cast(&value)) + , is_rvalue_(false) + {} + + json_ref(std::initializer_list init) + : owned_value_(init) + , is_rvalue_(true) + { + value_ref_ = &owned_value_; + } + + template + json_ref(Args... args) + : owned_value_(std::forward(args)...) + , is_rvalue_(true) + { + value_ref_ = &owned_value_; + } + + value_type moved_or_copied() const + { + if (is_rvalue_) + { + return std::move(*value_ref_); + } + else + { + return *value_ref_; + } + } + + value_type const& operator*() const + { + return *static_cast(value_ref_); + } + + value_type const* operator->() const + { + return static_cast(value_ref_); + } + + private: + value_type * value_ref_; + mutable value_type owned_value_; + bool is_rvalue_; +}; + } // namespace detail /// namespace to hold default `to_json` / `from_json` functions @@ -7297,6 +7400,7 @@ class basic_json template using json_serializer = JSONSerializer; + using initializer_list_t = std::initializer_list>; //////////////// // exceptions // @@ -8025,18 +8129,36 @@ class basic_json string = create(value); } + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + /// constructor for objects json_value(const object_t& value) { object = create(value); } + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + /// constructor for arrays json_value(const array_t& value) { array = create(value); } + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + void destroy(value_t t) { switch (t) @@ -8310,10 +8432,10 @@ class basic_json With the rules described above, the following JSON values cannot be expressed by an initializer list: - - the empty array (`[]`): use @ref array(std::initializer_list) + - the empty array (`[]`): use @ref array(initializer_list_t) with an empty initializer list in this case - arrays whose elements satisfy rule 2: use @ref - array(std::initializer_list) with the same initializer list + array(initializer_list_t) with the same initializer list in this case @note When used without parentheses around an empty initializer list, @ref @@ -8325,8 +8447,8 @@ class basic_json @param[in] type_deduction internal parameter; when set to `true`, the type of the JSON value is deducted from the initializer list @a init; when set to `false`, the type provided via @a manual_type is forced. This mode is - used by the functions @ref array(std::initializer_list) and - @ref object(std::initializer_list). + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). @param[in] manual_type internal parameter; when @a type_deduction is set to `false`, the created JSON value will use the provided type (only @ref @@ -8337,7 +8459,7 @@ class basic_json `value_t::object`, but @a init contains an element which is not a pair whose first element is a string. In this case, the constructor could not create an object. If @a type_deduction would have be `true`, an array - would have been created. See @ref object(std::initializer_list) + would have been created. See @ref object(initializer_list_t) for an example. @complexity Linear in the size of the initializer list @a init. @@ -8345,23 +8467,23 @@ class basic_json @liveexample{The example below shows how JSON values are created from initializer lists.,basic_json__list_init_t} - @sa @ref array(std::initializer_list) -- create a JSON array + @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object + @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ - basic_json(std::initializer_list init, + basic_json(initializer_list_t init, bool type_deduction = true, value_t manual_type = value_t::array) { // check if each element is an array with two elements whose first // element is a string bool is_an_object = std::all_of(init.begin(), init.end(), - [](const basic_json & element) + [](const detail::json_ref& element_ref) { - return (element.is_array() and element.size() == 2 and element[0].is_string()); + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); }); // adjust type if type deduction is not wanted @@ -8386,16 +8508,19 @@ class basic_json m_type = value_t::object; m_value = value_t::object; - std::for_each(init.begin(), init.end(), [this](const basic_json & element) + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) { - m_value.object->emplace(*(element[0].m_value.string), element[1]); + basic_json element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); }); } else { // the initializer list describes an array -> create array m_type = value_t::array; - m_value.array = create(init); + m_value.array = create(init.begin(), init.end()); } assert_invariant(); @@ -8410,7 +8535,7 @@ class basic_json @note This function is only needed to express two edge cases that cannot be realized with the initializer list constructor (@ref - basic_json(std::initializer_list, bool, value_t)). These cases + basic_json(initializer_list_t, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a string -- in this case, the initializer list constructor would create an @@ -8428,15 +8553,14 @@ class basic_json @liveexample{The following code shows an example for the `array` function.,array} - @sa @ref basic_json(std::initializer_list, bool, value_t) -- + @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list - @sa @ref object(std::initializer_list) -- create a JSON object + @sa @ref object(initializer_list_t) -- create a JSON object value from an initializer list @since version 1.0.0 */ - static basic_json array(std::initializer_list init = - std::initializer_list()) + static basic_json array(initializer_list_t init = {}) { return basic_json(init, false, value_t::array); } @@ -8449,10 +8573,10 @@ class basic_json the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the - related function @ref array(std::initializer_list), there are + related function @ref array(initializer_list_t), there are no cases which can only be expressed by this function. That is, any initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(std::initializer_list, bool, value_t). + constructor @ref basic_json(initializer_list_t, bool, value_t). @param[in] init initializer list to create an object from (optional) @@ -8460,7 +8584,7 @@ class basic_json @throw type_error.301 if @a init is not a list of pairs whose first elements are strings. In this case, no object can be created. When such a - value is passed to @ref basic_json(std::initializer_list, bool, value_t), + value is passed to @ref basic_json(initializer_list_t, bool, value_t), an array would have been created from the passed initializer list @a init. See example below. @@ -8469,15 +8593,14 @@ class basic_json @liveexample{The following code shows an example for the `object` function.,object} - @sa @ref basic_json(std::initializer_list, bool, value_t) -- + @sa @ref basic_json(initializer_list_t, bool, value_t) -- create a JSON value from an initializer list - @sa @ref array(std::initializer_list) -- create a JSON array + @sa @ref array(initializer_list_t) -- create a JSON array value from an initializer list @since version 1.0.0 */ - static basic_json object(std::initializer_list init = - std::initializer_list()) + static basic_json object(initializer_list_t init = {}) { return basic_json(init, false, value_t::object); } @@ -8650,6 +8773,11 @@ class basic_json // other constructors and destructor // /////////////////////////////////////// + basic_json(const detail::json_ref& ref) + : basic_json(ref.moved_or_copied()) + { + } + /*! @brief copy constructor @@ -11450,7 +11578,7 @@ class basic_json @brief add an object to an array @copydoc push_back(basic_json&&) */ - reference operator+=(basic_json&& val) + reference operator+=(basic_json && val) { push_back(std::move(val)); return *this; @@ -11565,12 +11693,13 @@ class basic_json @liveexample{The example shows how initializer lists are treated as objects when possible.,push_back__initializer_list} */ - void push_back(std::initializer_list init) + void push_back(initializer_list_t init) { - if (is_object() and init.size() == 2 and init.begin()->is_string()) + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) { - const string_t key = *init.begin(); - push_back(typename object_t::value_type(key, *(init.begin() + 1))); + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); } else { @@ -11580,9 +11709,9 @@ class basic_json /*! @brief add an object to an object - @copydoc push_back(std::initializer_list) + @copydoc push_back(initializer_list_t) */ - reference operator+=(std::initializer_list init) + reference operator+=(initializer_list_t init) { push_back(init); return *this; @@ -11867,7 +11996,7 @@ class basic_json @since version 1.0.0 */ - iterator insert(const_iterator pos, std::initializer_list ilist) + iterator insert(const_iterator pos, initializer_list_t ilist) { // insert only works for arrays if (JSON_UNLIKELY(not is_array())) @@ -11883,7 +12012,7 @@ class basic_json // insert to array and return iterator iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); return result; } diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index 7822b6344..5130e505c 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -842,8 +842,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l; - json j(l); + json j(json::initializer_list_t {}); CHECK(j.type() == json::value_t::object); } @@ -860,8 +859,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(json::array_t())}; - json j(l); + json j(json::initializer_list_t {json(json::array_t())}); CHECK(j.type() == json::value_t::array); } @@ -876,8 +874,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(json::object_t())}; - json j(l); + json j(json::initializer_list_t {json(json::object_t())}); CHECK(j.type() == json::value_t::array); } @@ -892,8 +889,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json("Hello world")}; - json j(l); + json j(json::initializer_list_t {json("Hello world")}); CHECK(j.type() == json::value_t::array); } @@ -908,8 +904,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(true)}; - json j(l); + json j(json::initializer_list_t {json(true)}); CHECK(j.type() == json::value_t::array); } @@ -924,8 +919,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(1)}; - json j(l); + json j(json::initializer_list_t {json(1)}); CHECK(j.type() == json::value_t::array); } @@ -940,8 +934,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(1u)}; - json j(l); + json j(json::initializer_list_t {json(1u)}); CHECK(j.type() == json::value_t::array); } @@ -956,8 +949,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {json(42.23)}; - json j(l); + json j(json::initializer_list_t {json(42.23)}); CHECK(j.type() == json::value_t::array); } @@ -973,8 +965,7 @@ TEST_CASE("constructors") { SECTION("explicit") { - std::initializer_list l = {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()}; - json j(l); + json j(json::initializer_list_t {1, 1u, 42.23, true, nullptr, json::object_t(), json::array_t()}); CHECK(j.type() == json::value_t::array); } @@ -1034,6 +1025,125 @@ TEST_CASE("constructors") CHECK(j.type() == json::value_t::array); } } + + SECTION("move from initializer_list") + { + SECTION("string") + { + // This should break through any short string optimization in std::string + std::string source(1024, '!'); + const char* source_addr = source.data(); + + SECTION("constructor with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(j[0].get_ref().data() == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(j["key"].get_ref().data() == source_addr); + } + + SECTION("constructor with implicit types (object key)") + { + json j = {{std::move(source), 42}}; + CHECK(j.get_ref().begin()->first.data() == source_addr); + } + } + + SECTION("array") + { + json::array_t source = {1, 2, 3}; + const json* source_addr = source.data(); + + SECTION("constructor with implicit types (array)") + { + json j {std::move(source)}; + CHECK(j[0].get_ref().data() == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j {{"key", std::move(source)}}; + CHECK(j["key"].get_ref().data() == source_addr); + } + + SECTION("assignment with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(j[0].get_ref().data() == source_addr); + } + + SECTION("assignment with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(j["key"].get_ref().data() == source_addr); + } + } + + SECTION("object") + { + json::object_t source = {{"hello", "world"}}; + const json* source_addr = &source.at("hello"); + + SECTION("constructor with implicit types (array)") + { + json j {std::move(source)}; + CHECK(&(j[0].get_ref().at("hello")) == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j {{"key", std::move(source)}}; + CHECK(&(j["key"].get_ref().at("hello")) == source_addr); + } + + SECTION("assignment with implicit types (array)") + { + json j = {std::move(source)}; + CHECK(&(j[0].get_ref().at("hello")) == source_addr); + } + + SECTION("assignment with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(&(j["key"].get_ref().at("hello")) == source_addr); + } + } + + SECTION("json") + { + json source {1, 2, 3}; + const json* source_addr = &source[0]; + + SECTION("constructor with implicit types (array)") + { + json j {std::move(source), {}}; + CHECK(&j[0][0] == source_addr); + } + + SECTION("constructor with implicit types (object)") + { + json j {{"key", std::move(source)}}; + CHECK(&j["key"][0] == source_addr); + } + + SECTION("assignment with implicit types (array)") + { + json j = {std::move(source), {}}; + CHECK(&j[0][0] == source_addr); + } + + SECTION("assignment with implicit types (object)") + { + json j = {{"key", std::move(source)}}; + CHECK(&j["key"][0] == source_addr); + } + } + + } } SECTION("create an array of n copies of a given value")