diff --git a/.github/workflows/vroom.yml b/.github/workflows/vroom.yml index 1e53625bb..5b630e765 100644 --- a/.github/workflows/vroom.yml +++ b/.github/workflows/vroom.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install libasio-dev libglpk-dev jq + sudo apt-get install libasio-dev libglpk-dev libboost-json1.83-dev jq - name: Build vroom run: make -j env: diff --git a/.github/workflows/vroom_libosrm.yml b/.github/workflows/vroom_libosrm.yml index 0789fcc71..c658ad634 100644 --- a/.github/workflows/vroom_libosrm.yml +++ b/.github/workflows/vroom_libosrm.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install libasio-dev libglpk-dev + sudo apt-get install libasio-dev libglpk-dev libboost-json1.83-dev - name: Cache OSRM id: cache uses: actions/cache@v4 diff --git a/.gitmodules b/.gitmodules index 7379cf80c..9b8014c8f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "include/polylineencoder"] path = include/polylineencoder url = https://github.com/vahancho/polylineencoder -[submodule "include/rapidjson"] - path = include/rapidjson - url = https://github.com/Tencent/rapidjson diff --git a/include/rapidjson b/include/rapidjson deleted file mode 160000 index 973dc9c06..000000000 --- a/include/rapidjson +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 diff --git a/libvroom_examples/makefile b/libvroom_examples/makefile index abf90256e..e6e065c59 100644 --- a/libvroom_examples/makefile +++ b/libvroom_examples/makefile @@ -1,6 +1,6 @@ CXX ?= g++ -CXXFLAGS = -I../src -std=c++20 -Wextra -Wpedantic -Wall -O3 -LDLIBS = -L../lib/ -lvroom -lpthread -lssl -lcrypto +CXXFLAGS = -I../src -std=c++20 -Wextra -Wpedantic -Wall -O3 -DBOOST_JSON_STANDALONE -DBOOST_JSON_NO_LIB +LDLIBS = -L../lib/ -lvroom -lpthread -lssl -lcrypto -lboost_json # Checking for libglpk based on whether the header file is found as # glpk does not provide a pkg-config setup. diff --git a/src/makefile b/src/makefile index 695ce248e..9fdb2160d 100644 --- a/src/makefile +++ b/src/makefile @@ -6,8 +6,8 @@ # Variables. CXX ?= g++ USE_ROUTING ?= true -CXXFLAGS = -MMD -MP -I. -std=c++20 -Wextra -Wpedantic -Wall -O3 -DASIO_STANDALONE -DUSE_ROUTING=$(USE_ROUTING) -LDLIBS = -lpthread +CXXFLAGS = -MMD -MP -I. -std=c++20 -Wextra -Wpedantic -Wall -O3 -DASIO_STANDALONE -DBOOST_JSON_STANDALONE -DBOOST_JSON_NO_LIB -DUSE_ROUTING=$(USE_ROUTING) +LDLIBS = -lpthread -lboost_json # Using all cpp files in current directory. MAIN = ../bin/vroom diff --git a/src/routing/http_wrapper.cpp b/src/routing/http_wrapper.cpp index 63847232b..6f6d996ec 100644 --- a/src/routing/http_wrapper.cpp +++ b/src/routing/http_wrapper.cpp @@ -139,12 +139,16 @@ std::string HttpWrapper::run_query(const std::string& query) const { : send_then_receive(query); } -void HttpWrapper::parse_response(rapidjson::Document& json_result, +void HttpWrapper::parse_response(boost::json::object& json_result, const std::string& json_content) { #ifdef NDEBUG - json_result.Parse(json_content.c_str()); + json_result = boost::json::parse(json_content).as_object(); #else - assert(!json_result.Parse(json_content.c_str()).HasParseError()); + boost::json::error_code ec; + auto content = boost::json::parse(json_content, ec); + boost::json::object* ptr = content.if_object(); + assert(!ec && ptr); + json_result = content.get_object(); #endif } @@ -155,19 +159,19 @@ Matrices HttpWrapper::get_matrices(const std::vector& locs) const { // Expected matrix size. std::size_t m_size = locs.size(); - rapidjson::Document json_result; + boost::json::object json_result; this->parse_response(json_result, json_string); this->check_response(json_result, locs, _matrix_service); - if (!json_result.HasMember(_matrix_durations_key.c_str())) { + if (!json_result.contains(_matrix_durations_key)) { throw RoutingException("Missing " + _matrix_durations_key + "."); } - assert(json_result[_matrix_durations_key.c_str()].Size() == m_size); + assert(json_result.at(_matrix_durations_key).get_array().size() == m_size); - if (!json_result.HasMember(_matrix_distances_key.c_str())) { + if (!json_result.contains(_matrix_distances_key)) { throw RoutingException("Missing " + _matrix_distances_key + "."); } - assert(json_result[_matrix_distances_key.c_str()].Size() == m_size); + assert(json_result.at(_matrix_distances_key).get_array().size() == m_size); // Build matrices while checking for unfound routes ('null' values) // to avoid unexpected behavior. @@ -176,22 +180,22 @@ Matrices HttpWrapper::get_matrices(const std::vector& locs) const { std::vector nb_unfound_from_loc(m_size, 0); std::vector nb_unfound_to_loc(m_size, 0); - for (rapidjson::SizeType i = 0; i < m_size; ++i) { - const auto& duration_line = json_result[_matrix_durations_key.c_str()][i]; - const auto& distance_line = json_result[_matrix_distances_key.c_str()][i]; - assert(duration_line.Size() == m_size); - assert(distance_line.Size() == m_size); - for (rapidjson::SizeType j = 0; j < m_size; ++j) { - if (duration_value_is_null(duration_line[j]) || - distance_value_is_null(distance_line[j])) { + for (size_t i = 0; i < m_size; ++i) { + const auto& duration_line = json_result.at(_matrix_durations_key).at(i); + const auto& distance_line = json_result.at(_matrix_distances_key).at(i); + assert(duration_line.get_array().size() == m_size); + assert(distance_line.get_array().size() == m_size); + for (size_t j = 0; j < m_size; ++j) { + if (duration_value_is_null(duration_line.at(j)) || + distance_value_is_null(distance_line.at(j))) { // No route found between i and j. Just storing info as we // don't know yet which location is responsible between i // and j. ++nb_unfound_from_loc[i]; ++nb_unfound_to_loc[j]; } else { - m.durations[i][j] = get_duration_value(duration_line[j]); - m.distances[i][j] = get_distance_value(distance_line[j]); + m.durations[i][j] = get_duration_value(duration_line.at(j)); + m.distances[i][j] = get_distance_value(distance_line.at(j)); } } } @@ -219,7 +223,7 @@ void HttpWrapper::add_geometry(Route& route) const { std::string json_string = this->run_query(query); - rapidjson::Document json_result; + boost::json::object json_result; parse_response(json_result, json_string); this->check_response(json_result, non_break_locations, // not supposed to be used diff --git a/src/routing/http_wrapper.h b/src/routing/http_wrapper.h index cdbd80989..6f1c8d626 100644 --- a/src/routing/http_wrapper.h +++ b/src/routing/http_wrapper.h @@ -9,7 +9,7 @@ Copyright (c) 2015-2024, Julien Coupey. All rights reserved (see LICENSE). */ -#include "../include/rapidjson/include/rapidjson/document.h" +#include #include "routing/wrapper.h" #include "structures/typedefs.h" @@ -42,33 +42,33 @@ class HttpWrapper : public Wrapper { std::string run_query(const std::string& query) const; - static void parse_response(rapidjson::Document& json_result, + static void parse_response(boost::json::object& json_result, const std::string& json_content); virtual std::string build_query(const std::vector& locations, const std::string& service) const = 0; - virtual void check_response(const rapidjson::Document& json_result, + virtual void check_response(const boost::json::object& json_result, const std::vector& locs, const std::string& service) const = 0; Matrices get_matrices(const std::vector& locs) const override; virtual bool - duration_value_is_null(const rapidjson::Value& matrix_entry) const = 0; + duration_value_is_null(const boost::json::value& matrix_entry) const = 0; virtual bool - distance_value_is_null(const rapidjson::Value& matrix_entry) const = 0; + distance_value_is_null(const boost::json::value& matrix_entry) const = 0; virtual UserDuration - get_duration_value(const rapidjson::Value& matrix_entry) const = 0; + get_duration_value(const boost::json::value& matrix_entry) const = 0; virtual UserDistance - get_distance_value(const rapidjson::Value& matrix_entry) const = 0; + get_distance_value(const boost::json::value& matrix_entry) const = 0; - virtual unsigned get_legs_number(const rapidjson::Value& result) const = 0; + virtual unsigned get_legs_number(const boost::json::object& result) const = 0; - virtual std::string get_geometry(rapidjson::Value& result) const = 0; + virtual std::string get_geometry(boost::json::object& result) const = 0; void add_geometry(Route& route) const override; }; diff --git a/src/routing/ors_wrapper.cpp b/src/routing/ors_wrapper.cpp index b9dbb6fa5..871a08cf3 100644 --- a/src/routing/ors_wrapper.cpp +++ b/src/routing/ors_wrapper.cpp @@ -10,6 +10,8 @@ All rights reserved (see LICENSE). #include "routing/ors_wrapper.h" #include "utils/helpers.h" +#include + namespace vroom::routing { OrsWrapper::OrsWrapper(const std::string& profile, const Server& server) @@ -60,41 +62,41 @@ std::string OrsWrapper::build_query(const std::vector& locations, return query; } -void OrsWrapper::check_response(const rapidjson::Document& json_result, +void OrsWrapper::check_response(const boost::json::object& json_result, const std::vector&, const std::string&) const { - if (json_result.HasMember("error")) { + if (json_result.contains("error")) { throw RoutingException( - std::string(json_result["error"]["message"].GetString())); + json_result.at("error").at("message").get_string().subview()); } } bool OrsWrapper::duration_value_is_null( - const rapidjson::Value& matrix_entry) const { - return matrix_entry.IsNull(); + const boost::json::value& matrix_entry) const { + return matrix_entry.is_null(); } bool OrsWrapper::distance_value_is_null( - const rapidjson::Value& matrix_entry) const { - return matrix_entry.IsNull(); + const boost::json::value& matrix_entry) const { + return matrix_entry.is_null(); } UserDuration -OrsWrapper::get_duration_value(const rapidjson::Value& matrix_entry) const { - return utils::round(matrix_entry.GetDouble()); +OrsWrapper::get_duration_value(const boost::json::value& matrix_entry) const { + return utils::round(matrix_entry.to_number()); } UserDistance -OrsWrapper::get_distance_value(const rapidjson::Value& matrix_entry) const { - return utils::round(matrix_entry.GetDouble()); +OrsWrapper::get_distance_value(const boost::json::value& matrix_entry) const { + return utils::round(matrix_entry.to_number()); } -unsigned OrsWrapper::get_legs_number(const rapidjson::Value& result) const { - return result["routes"][0]["segments"].Size(); +unsigned OrsWrapper::get_legs_number(const boost::json::object& result) const { + return result.at("routes").at(0).at("segments").get_array().size(); } -std::string OrsWrapper::get_geometry(rapidjson::Value& result) const { - return result["routes"][0]["geometry"].GetString(); +std::string OrsWrapper::get_geometry(boost::json::object& result) const { + return result.at("routes").at(0).at("geometry").get_string().subview(); } } // namespace vroom::routing diff --git a/src/routing/ors_wrapper.h b/src/routing/ors_wrapper.h index a08e5e50d..6ce2c10e7 100644 --- a/src/routing/ors_wrapper.h +++ b/src/routing/ors_wrapper.h @@ -19,25 +19,25 @@ class OrsWrapper : public HttpWrapper { std::string build_query(const std::vector& locations, const std::string& service) const override; - void check_response(const rapidjson::Document& json_result, + void check_response(const boost::json::object& json_result, const std::vector& locs, const std::string& service) const override; bool - duration_value_is_null(const rapidjson::Value& matrix_entry) const override; + duration_value_is_null(const boost::json::value& matrix_entry) const override; bool - distance_value_is_null(const rapidjson::Value& matrix_entry) const override; + distance_value_is_null(const boost::json::value& matrix_entry) const override; UserDuration - get_duration_value(const rapidjson::Value& matrix_entry) const override; + get_duration_value(const boost::json::value& matrix_entry) const override; UserDistance - get_distance_value(const rapidjson::Value& matrix_entry) const override; + get_distance_value(const boost::json::value& matrix_entry) const override; - unsigned get_legs_number(const rapidjson::Value& result) const override; + unsigned get_legs_number(const boost::json::object& result) const override; - std::string get_geometry(rapidjson::Value& result) const override; + std::string get_geometry(boost::json::object& result) const override; public: OrsWrapper(const std::string& profile, const Server& server); diff --git a/src/routing/osrm_routed_wrapper.cpp b/src/routing/osrm_routed_wrapper.cpp index e44f9d3f0..adb3315e2 100644 --- a/src/routing/osrm_routed_wrapper.cpp +++ b/src/routing/osrm_routed_wrapper.cpp @@ -62,13 +62,14 @@ OsrmRoutedWrapper::build_query(const std::vector& locations, return query; } -void OsrmRoutedWrapper::check_response(const rapidjson::Document& json_result, +void OsrmRoutedWrapper::check_response(const boost::json::object& json_result, const std::vector& locs, const std::string&) const { - assert(json_result.HasMember("code")); - const std::string code = json_result["code"].GetString(); + assert(json_result.contains("code")); + const std::string code = json_result.at("code").get_string().subview(); if (code != "Ok") { - const std::string message = json_result["message"].GetString(); + const std::string message = + json_result.at("message").get_string().subview(); if (const std::string snapping_error_base = "Could not find a matching segment for coordinate "; code == "NoSegment" && message.starts_with(snapping_error_base)) { @@ -87,32 +88,32 @@ void OsrmRoutedWrapper::check_response(const rapidjson::Document& json_result, } bool OsrmRoutedWrapper::duration_value_is_null( - const rapidjson::Value& matrix_entry) const { - return matrix_entry.IsNull(); + const boost::json::value& matrix_entry) const { + return matrix_entry.is_null(); } bool OsrmRoutedWrapper::distance_value_is_null( - const rapidjson::Value& matrix_entry) const { - return matrix_entry.IsNull(); + const boost::json::value& matrix_entry) const { + return matrix_entry.is_null(); } UserDuration OsrmRoutedWrapper::get_duration_value( - const rapidjson::Value& matrix_entry) const { - return utils::round(matrix_entry.GetDouble()); + const boost::json::value& matrix_entry) const { + return utils::round(matrix_entry.to_number()); } UserDistance OsrmRoutedWrapper::get_distance_value( - const rapidjson::Value& matrix_entry) const { - return utils::round(matrix_entry.GetDouble()); + const boost::json::value& matrix_entry) const { + return utils::round(matrix_entry.to_number()); } unsigned -OsrmRoutedWrapper::get_legs_number(const rapidjson::Value& result) const { - return result["routes"][0]["legs"].Size(); +OsrmRoutedWrapper::get_legs_number(const boost::json::object& result) const { + return result.at("routes").at(0).at("legs").get_array().size(); } -std::string OsrmRoutedWrapper::get_geometry(rapidjson::Value& result) const { - return result["routes"][0]["geometry"].GetString(); +std::string OsrmRoutedWrapper::get_geometry(boost::json::object& result) const { + return result.at("routes").at(0).at("geometry").get_string().subview(); } } // namespace vroom::routing diff --git a/src/routing/osrm_routed_wrapper.h b/src/routing/osrm_routed_wrapper.h index e2dea1e3f..1395211b9 100644 --- a/src/routing/osrm_routed_wrapper.h +++ b/src/routing/osrm_routed_wrapper.h @@ -19,25 +19,25 @@ class OsrmRoutedWrapper : public HttpWrapper { std::string build_query(const std::vector& locations, const std::string& service) const override; - void check_response(const rapidjson::Document& json_result, + void check_response(const boost::json::object& json_result, const std::vector& locs, const std::string& service) const override; bool - duration_value_is_null(const rapidjson::Value& matrix_entry) const override; + duration_value_is_null(const boost::json::value& matrix_entry) const override; bool - distance_value_is_null(const rapidjson::Value& matrix_entry) const override; + distance_value_is_null(const boost::json::value& matrix_entry) const override; UserDuration - get_duration_value(const rapidjson::Value& matrix_entry) const override; + get_duration_value(const boost::json::value& matrix_entry) const override; UserDistance - get_distance_value(const rapidjson::Value& matrix_entry) const override; + get_distance_value(const boost::json::value& matrix_entry) const override; - unsigned get_legs_number(const rapidjson::Value& result) const override; + unsigned get_legs_number(const boost::json::object& result) const override; - std::string get_geometry(rapidjson::Value& result) const override; + std::string get_geometry(boost::json::object& result) const override; public: OsrmRoutedWrapper(const std::string& profile, const Server& server); diff --git a/src/routing/valhalla_wrapper.cpp b/src/routing/valhalla_wrapper.cpp index d73353665..2692aa3fd 100644 --- a/src/routing/valhalla_wrapper.cpp +++ b/src/routing/valhalla_wrapper.cpp @@ -87,15 +87,15 @@ std::string ValhallaWrapper::build_query(const std::vector& locations, : get_route_query(locations); } -void ValhallaWrapper::check_response(const rapidjson::Document& json_result, +void ValhallaWrapper::check_response(const boost::json::object& json_result, const std::vector&, const std::string& service) const { assert(service == _matrix_service || service == _route_service); if (constexpr unsigned HTTP_OK = 200; - json_result.HasMember("status_code") && - json_result["status_code"].IsUint() && - json_result["status_code"].GetUint() != HTTP_OK) { + json_result.contains("status_code") && + json_result.at("status_code").is_number() && + json_result.at("status_code").to_number() != HTTP_OK) { // Valhalla responses seem to only have a status_code key when a // problem is encountered. In that case it's not really clear what // keys can be expected so we're playing guesses. This happens @@ -104,54 +104,54 @@ void ValhallaWrapper::check_response(const rapidjson::Document& json_result, std::string service_str = (service == _route_service) ? "route" : "matrix"; std::string error = "Valhalla " + service_str + " error ("; - if (json_result.HasMember("error") && json_result["error"].IsString()) { - error += json_result["error"].GetString(); + if (json_result.contains("error") && json_result.at("error").is_string()) { + error += json_result.at("error").get_string().subview(); error += ")."; } throw RoutingException(error); } if (service == _route_service) { - assert(json_result.HasMember("trip") && - json_result["trip"].HasMember("status")); - if (json_result["trip"]["status"] != 0) { + assert(json_result.contains("trip") && + json_result.at("trip").get_object().contains("status")); + if (json_result.at("trip").at("status") != 0) { throw RoutingException( - std::string(json_result["trip"]["status_message"].GetString())); + json_result.at("trip").at("status_message").get_string().subview()); } } } bool ValhallaWrapper::duration_value_is_null( - const rapidjson::Value& matrix_entry) const { - assert(matrix_entry.HasMember("time")); - return matrix_entry["time"].IsNull(); + const boost::json::value& matrix_entry) const { + assert(matrix_entry.get_object().contains("time")); + return matrix_entry.at("time").is_null(); } bool ValhallaWrapper::distance_value_is_null( - const rapidjson::Value& matrix_entry) const { - assert(matrix_entry.HasMember("distance")); - return matrix_entry["distance"].IsNull(); + const boost::json::value& matrix_entry) const { + assert(matrix_entry.get_object().contains("distance")); + return matrix_entry.at("distance").is_null(); } UserDuration ValhallaWrapper::get_duration_value( - const rapidjson::Value& matrix_entry) const { - assert(matrix_entry["time"].IsUint()); - return matrix_entry["time"].GetUint(); + const boost::json::value& matrix_entry) const { + assert(matrix_entry.at("time").is_number()); + return matrix_entry.to_number(); } UserDistance ValhallaWrapper::get_distance_value( - const rapidjson::Value& matrix_entry) const { - assert(matrix_entry["distance"].IsDouble()); - return utils::round(km_to_m * - matrix_entry["distance"].GetDouble()); + const boost::json::value& matrix_entry) const { + assert(matrix_entry.at("distance").is_number()); + return utils::round( + km_to_m * matrix_entry.at("distance").to_number()); } unsigned -ValhallaWrapper::get_legs_number(const rapidjson::Value& result) const { - return result["trip"]["legs"].Size(); +ValhallaWrapper::get_legs_number(const boost::json::object& result) const { + return result.at("trip").at("legs").get_array().size(); } -std::string ValhallaWrapper::get_geometry(rapidjson::Value& result) const { +std::string ValhallaWrapper::get_geometry(boost::json::object& result) const { // Valhalla returns one polyline per route leg so we need to merge // them. Also taking the opportunity to adjust the encoding // precision as Valhalla uses 6 and we use 5 based on other routing @@ -163,12 +163,12 @@ std::string ValhallaWrapper::get_geometry(rapidjson::Value& result) const { auto full_polyline = gepaf::PolylineEncoder::decode( - result["trip"]["legs"][0]["shape"].GetString()); + result.at("trip").at("legs").at(0).at("shape").get_string().subview()); - for (rapidjson::SizeType i = 1; i < result["trip"]["legs"].Size(); ++i) { + for (size_t i = 1; i < result.at("trip").at("legs").get_array().size(); ++i) { auto decoded_pts = gepaf::PolylineEncoder::decode( - result["trip"]["legs"][i]["shape"].GetString()); + result.at("trip").at("legs").at(i).at("shape").get_string().subview()); if (!full_polyline.empty()) { full_polyline.pop_back(); diff --git a/src/routing/valhalla_wrapper.h b/src/routing/valhalla_wrapper.h index a3be2b502..6ee7cbe35 100644 --- a/src/routing/valhalla_wrapper.h +++ b/src/routing/valhalla_wrapper.h @@ -23,25 +23,25 @@ class ValhallaWrapper : public HttpWrapper { std::string build_query(const std::vector& locations, const std::string& service) const override; - void check_response(const rapidjson::Document& json_result, + void check_response(const boost::json::object& json_result, const std::vector& locs, const std::string& service) const override; bool - duration_value_is_null(const rapidjson::Value& matrix_entry) const override; + duration_value_is_null(const boost::json::value& matrix_entry) const override; bool - distance_value_is_null(const rapidjson::Value& matrix_entry) const override; + distance_value_is_null(const boost::json::value& matrix_entry) const override; UserDuration - get_duration_value(const rapidjson::Value& matrix_entry) const override; + get_duration_value(const boost::json::value& matrix_entry) const override; UserDistance - get_distance_value(const rapidjson::Value& matrix_entry) const override; + get_distance_value(const boost::json::value& matrix_entry) const override; - unsigned get_legs_number(const rapidjson::Value& result) const override; + unsigned get_legs_number(const boost::json::object& result) const override; - std::string get_geometry(rapidjson::Value& result) const override; + std::string get_geometry(boost::json::object& result) const override; public: ValhallaWrapper(const std::string& profile, const Server& server); diff --git a/src/utils/input_parser.cpp b/src/utils/input_parser.cpp index 83f64a092..1591f59ae 100644 --- a/src/utils/input_parser.cpp +++ b/src/utils/input_parser.cpp @@ -8,182 +8,192 @@ All rights reserved (see LICENSE). */ #include - -#include "../include/rapidjson/include/rapidjson/document.h" -#include "../include/rapidjson/include/rapidjson/error/en.h" +#include #include "utils/input_parser.h" namespace vroom::io { // Helper to get optional array of coordinates. -inline Coordinates parse_coordinates(const rapidjson::Value& object, +inline Coordinates parse_coordinates(const boost::json::object& object, const char* key) { - if (!object[key].IsArray() || (object[key].Size() < 2) || - !object[key][0].IsNumber() || !object[key][1].IsNumber()) { + boost::json::array const array = object.at(key).get_array(); + if ((array.size() < 2) || !array[0].is_number() || !array[1].is_number()) { throw InputException("Invalid " + std::string(key) + " array."); } - return {object[key][0].GetDouble(), object[key][1].GetDouble()}; + return {array[0].to_number(), + array[1].to_number()}; } -inline std::string get_string(const rapidjson::Value& object, const char* key) { +inline std::string get_string(const boost::json::object& object, + const char* key) { std::string value; - if (object.HasMember(key)) { - if (!object[key].IsString()) { + if (object.contains(key)) { + if (!object.at(key).is_string()) { throw InputException("Invalid " + std::string(key) + " value."); } - value = object[key].GetString(); + value = object.at(key).get_string().subview(); } return value; } -inline double get_double(const rapidjson::Value& object, const char* key) { +inline double get_double(const boost::json::object& object, const char* key) { double value = 1.; - if (object.HasMember(key)) { - if (!object[key].IsNumber()) { + if (object.contains(key)) { + if (!object.at(key).is_number()) { throw InputException("Invalid " + std::string(key) + " value."); } - value = object[key].GetDouble(); + value = object.at(key).to_number(); } return value; } -inline Amount get_amount(const rapidjson::Value& object, +inline Amount get_amount(const boost::json::object& object, const char* key, unsigned amount_size) { // Default to zero amount with provided size. Amount amount(amount_size); - if (object.HasMember(key)) { - if (!object[key].IsArray()) { + if (object.contains(key)) { + if (!object.at(key).is_array()) { throw InputException("Invalid " + std::string(key) + " array."); } - if (object[key].Size() != amount_size) { + boost::json::array const array = object.at(key).get_array(); + + if (array.size() != amount_size) { throw InputException(std::format("Inconsistent {} length: {} and {}.", key, - object[key].Size(), + array.size(), amount_size)); } - for (rapidjson::SizeType i = 0; i < object[key].Size(); ++i) { - if (!object[key][i].IsUint()) { + for (size_t i = 0; i < array.size(); ++i) { + if (!array[i].is_number()) { throw InputException("Invalid " + std::string(key) + " value."); } - amount[i] = object[key][i].GetUint(); + amount[i] = array[i].to_number(); } } return amount; } -inline Skills get_skills(const rapidjson::Value& object) { +inline Skills get_skills(const boost::json::object& object) { Skills skills; - if (object.HasMember("skills")) { - if (!object["skills"].IsArray()) { + if (object.contains("skills")) { + if (!object.at("skills").is_array()) { throw InputException("Invalid skills object."); } - for (rapidjson::SizeType i = 0; i < object["skills"].Size(); ++i) { - if (!object["skills"][i].IsUint()) { + + boost::json::array const array = object.at("skills").get_array(); + + for (size_t i = 0; i < array.size(); ++i) { + if (!array[i].is_number()) { throw InputException("Invalid skill value."); } - skills.insert(object["skills"][i].GetUint()); + skills.insert(array[i].to_number()); } } return skills; } -inline UserDuration get_duration(const rapidjson::Value& object, +inline UserDuration get_duration(const boost::json::object& object, const char* key) { UserDuration duration = 0; - if (object.HasMember(key)) { - if (!object[key].IsUint()) { + if (object.contains(key)) { + if (!object.at(key).is_number()) { throw InputException("Invalid " + std::string(key) + " duration."); } - duration = object[key].GetUint(); + duration = object.at(key).to_number(); } return duration; } -inline Priority get_priority(const rapidjson::Value& object) { +inline Priority get_priority(const boost::json::object& object) { Priority priority = 0; - if (object.HasMember("priority")) { - if (!object["priority"].IsUint()) { + if (object.contains("priority")) { + if (!object.at("priority").is_number()) { throw InputException("Invalid priority value."); } - priority = object["priority"].GetUint(); + priority = object.at("priority").to_number(); } return priority; } template -inline std::optional get_value_for(const rapidjson::Value& object, +inline std::optional get_value_for(const boost::json::object& object, const char* key) { std::optional value; - if (object.HasMember(key)) { - if (!object[key].IsUint()) { + if (object.contains(key)) { + if (!object.at(key).is_number()) { throw InputException("Invalid " + std::string(key) + " value."); } - value = object[key].GetUint(); + value = object.at(key).to_number(); } return value; } -inline void check_id(const rapidjson::Value& v, const std::string& type) { - if (!v.IsObject()) { +inline void check_id(const boost::json::value& v, const std::string& type) { + if (!v.is_object()) { throw InputException("Invalid " + type + "."); } - if (!v.HasMember("id") || !v["id"].IsUint64()) { + if (!v.get_object().contains("id") || !v.at("id").is_number()) { throw InputException("Invalid or missing id for " + type + "."); } } -inline void check_shipment(const rapidjson::Value& v) { - if (!v.IsObject()) { +inline void check_shipment(const boost::json::value& v) { + if (!v.is_object()) { throw InputException("Invalid shipment."); } - if (!v.HasMember("pickup") || !v["pickup"].IsObject()) { + if (!v.get_object().contains("pickup") || !v.at("pickup").is_object()) { throw InputException("Missing pickup for shipment."); } - if (!v.HasMember("delivery") || !v["delivery"].IsObject()) { + if (!v.get_object().contains("delivery") || !v.at("delivery").is_object()) { throw InputException("Missing delivery for shipment."); } } -inline void check_location(const rapidjson::Value& v, const std::string& type) { - if (!v.HasMember("location") || !v["location"].IsArray()) { - throw InputException( - std::format("Invalid location for {} {}.", type, v["id"].GetUint64())); +inline void check_location(const boost::json::object& v, + const std::string& type) { + if (!v.contains("location") || !v.at("location").is_array()) { + throw InputException(std::format("Invalid location for {} {}.", + type, + v.at("id").to_number())); } } -inline TimeWindow get_time_window(const rapidjson::Value& tw) { - if (!tw.IsArray() || tw.Size() < 2 || !tw[0].IsUint() || !tw[1].IsUint()) { +inline TimeWindow get_time_window(const boost::json::value& tw) { + if (!tw.is_array() || tw.get_array().size() < 2 || !tw.at(0).is_number() || + !tw.at(1).is_number()) { throw InputException("Invalid time-window."); } - return TimeWindow(tw[0].GetUint(), tw[1].GetUint()); + return TimeWindow(tw.at(0).to_number(), + tw.at(1).to_number()); } -inline TimeWindow get_vehicle_time_window(const rapidjson::Value& v) { +inline TimeWindow get_vehicle_time_window(const boost::json::object& v) { TimeWindow v_tw; - if (v.HasMember("time_window")) { - v_tw = get_time_window(v["time_window"]); + if (v.contains("time_window")) { + v_tw = get_time_window(v.at("time_window")); } return v_tw; } -inline std::vector get_time_windows(const rapidjson::Value& o) { +inline std::vector get_time_windows(const boost::json::object& o) { std::vector tws; - if (o.HasMember("time_windows")) { - if (!o["time_windows"].IsArray() || o["time_windows"].Empty()) { + if (o.contains("time_windows")) { + if (!o.at("time_windows").is_array() || + o.at("time_windows").get_array().empty()) { throw InputException( std::format("Invalid time_windows array for object {}.", - o["id"].GetUint64())); + o.at("id").to_number())); } - std::transform(o["time_windows"].Begin(), - o["time_windows"].End(), + std::transform(o.at("time_windows").get_array().begin(), + o.at("time_windows").get_array().end(), std::back_inserter(tws), [](auto& tw) { return get_time_window(tw); }); @@ -195,31 +205,31 @@ inline std::vector get_time_windows(const rapidjson::Value& o) { return tws; } -inline Break get_break(const rapidjson::Value& b, unsigned amount_size) { +inline Break get_break(const boost::json::value& b, unsigned amount_size) { check_id(b, "break"); - const auto max_load = b.HasMember("max_load") - ? get_amount(b, "max_load", amount_size) + const auto max_load = b.get_object().contains("max_load") + ? get_amount(b.get_object(), "max_load", amount_size) : std::optional(); - return Break(b["id"].GetUint64(), - get_time_windows(b), - get_duration(b, "service"), - get_string(b, "description"), + return Break(b.at("id").to_number(), + get_time_windows(b.get_object()), + get_duration(b.get_object(), "service"), + get_string(b.get_object(), "description"), max_load); } -inline std::vector get_vehicle_breaks(const rapidjson::Value& v, +inline std::vector get_vehicle_breaks(const boost::json::object& v, unsigned amount_size) { std::vector breaks; - if (v.HasMember("breaks")) { - if (!v["breaks"].IsArray()) { - throw InputException( - std::format("Invalid breaks for vehicle {}.", v["id"].GetUint64())); + if (v.contains("breaks")) { + if (!v.at("breaks").is_array()) { + throw InputException(std::format("Invalid breaks for vehicle {}.", + v.at("id").to_number())); } - std::transform(v["breaks"].Begin(), - v["breaks"].End(), + std::transform(v.at("breaks").get_array().begin(), + v.at("breaks").get_array().end(), std::back_inserter(breaks), [&](auto& b) { return get_break(b, amount_size); }); } @@ -232,86 +242,87 @@ inline std::vector get_vehicle_breaks(const rapidjson::Value& v, return breaks; } -inline VehicleCosts get_vehicle_costs(const rapidjson::Value& v) { +inline VehicleCosts get_vehicle_costs(const boost::json::object& v) { UserCost fixed = 0; UserCost per_hour = DEFAULT_COST_PER_HOUR; UserCost per_km = DEFAULT_COST_PER_KM; - if (v.HasMember("costs")) { - if (!v["costs"].IsObject()) { - throw InputException( - std::format("Invalid costs for vehicle {}.", v["id"].GetUint64())); + if (v.contains("costs")) { + if (!v.at("costs").is_object()) { + throw InputException(std::format("Invalid costs for vehicle {}.", + v.at("id").to_number())); } - if (v["costs"].HasMember("fixed")) { - if (!v["costs"]["fixed"].IsUint()) { + if (v.at("costs").get_object().contains("fixed")) { + if (!v.at("costs").at("fixed").is_number()) { throw InputException(std::format("Invalid fixed cost for vehicle {}.", - v["id"].GetUint64())); + v.at("id").to_number())); } - fixed = v["costs"]["fixed"].GetUint(); + fixed = v.at("costs").at("fixed").to_number(); } - if (v["costs"].HasMember("per_hour")) { - if (!v["costs"]["per_hour"].IsUint()) { + if (v.at("costs").get_object().contains("per_hour")) { + if (!v.at("costs").at("per_hour").is_number()) { throw InputException( std::format("Invalid per_hour cost for vehicle {}.", - v["id"].GetUint64())); + v.at("id").to_number())); } - per_hour = v["costs"]["per_hour"].GetUint(); + per_hour = v.at("costs").at("per_hour").to_number(); } - if (v["costs"].HasMember("per_km")) { - if (!v["costs"]["per_km"].IsUint()) { + if (v.at("costs").get_object().contains("per_km")) { + if (!v.at("costs").at("per_km").is_number()) { throw InputException(std::format("Invalid per_km cost for vehicle {}.", - v["id"].GetUint64())); + v.at("id").to_number())); } - per_km = v["costs"]["per_km"].GetUint(); + per_km = v.at("costs").at("per_km").to_number(); } } return VehicleCosts(fixed, per_hour, per_km); } -inline std::vector get_vehicle_steps(const rapidjson::Value& v) { +inline std::vector +get_vehicle_steps(const boost::json::object& v) { std::vector steps; - if (v.HasMember("steps")) { - if (!v["steps"].IsArray()) { - throw InputException( - std::format("Invalid steps for vehicle {}.", v["id"].GetUint64())); + if (v.contains("steps")) { + if (!v.at("steps").is_array()) { + throw InputException(std::format("Invalid steps for vehicle {}.", + v.at("id").to_number())); } - steps.reserve(v["steps"].Size()); + steps.reserve(v.at("steps").get_array().size()); - for (rapidjson::SizeType i = 0; i < v["steps"].Size(); ++i) { - const auto& json_step = v["steps"][i]; + for (size_t i = 0; i < v.at("steps").get_array().size(); ++i) { + const auto& json_step = v.at("steps").at(i).get_object(); std::optional at; - if (json_step.HasMember("service_at")) { - if (!json_step["service_at"].IsUint()) { + if (json_step.contains("service_at")) { + if (!json_step.at("service_at").is_number()) { throw InputException("Invalid service_at value."); } - at = json_step["service_at"].GetUint(); + at = json_step.at("service_at").to_number(); } std::optional after; - if (json_step.HasMember("service_after")) { - if (!json_step["service_after"].IsUint()) { + if (json_step.contains("service_after")) { + if (!json_step.at("service_after").is_number()) { throw InputException("Invalid service_after value."); } - after = json_step["service_after"].GetUint(); + after = json_step.at("service_after").to_number(); } std::optional before; - if (json_step.HasMember("service_before")) { - if (!json_step["service_before"].IsUint()) { + if (json_step.contains("service_before")) { + if (!json_step.at("service_before").is_number()) { throw InputException("Invalid service_before value."); } - before = json_step["service_before"].GetUint(); + before = json_step.at("service_before").to_number(); } ForcedService forced_service(at, after, before); @@ -326,31 +337,31 @@ inline std::vector get_vehicle_steps(const rapidjson::Value& v) { continue; } - if (!json_step.HasMember("id") || !json_step["id"].IsUint64()) { + if (!json_step.contains("id") || !json_step.at("id").is_number()) { throw InputException(std::format("Invalid id in steps for vehicle {}.", - v["id"].GetUint64())); + v.at("id").to_number())); } if (type_str == "job") { steps.emplace_back(JOB_TYPE::SINGLE, - json_step["id"].GetUint64(), + json_step.at("id").to_number(), std::move(forced_service)); } else if (type_str == "pickup") { steps.emplace_back(JOB_TYPE::PICKUP, - json_step["id"].GetUint64(), + json_step.at("id").to_number(), std::move(forced_service)); } else if (type_str == "delivery") { steps.emplace_back(JOB_TYPE::DELIVERY, - json_step["id"].GetUint64(), + json_step.at("id").to_number(), std::move(forced_service)); } else if (type_str == "break") { steps.emplace_back(STEP_TYPE::BREAK, - json_step["id"].GetUint64(), + json_step.at("id").to_number(), std::move(forced_service)); } else { throw InputException( std::format("Invalid type in steps for vehicle {}.", - v["id"].GetUint64())); + v.at("id").to_number())); } } } @@ -358,16 +369,16 @@ inline std::vector get_vehicle_steps(const rapidjson::Value& v) { return steps; } -inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, +inline Vehicle get_vehicle(const boost::json::object& json_vehicle, unsigned amount_size) { check_id(json_vehicle, "vehicle"); - auto v_id = json_vehicle["id"].GetUint64(); + auto v_id = json_vehicle.at("id").to_number(); // Check what info are available for vehicle start, then build // optional start location. - bool has_start_coords = json_vehicle.HasMember("start"); - bool has_start_index = json_vehicle.HasMember("start_index"); - if (has_start_index && !json_vehicle["start_index"].IsUint()) { + bool has_start_coords = json_vehicle.contains("start"); + bool has_start_index = json_vehicle.contains("start_index"); + if (has_start_index && !json_vehicle.at("start_index").is_number()) { throw InputException( std::format("Invalid start_index for vehicle {}.", v_id)); } @@ -375,7 +386,7 @@ inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, std::optional start; if (has_start_index) { // Custom provided matrices and index. - Index start_index = json_vehicle["start_index"].GetUint(); + Index start_index = json_vehicle.at("start_index").to_number(); if (has_start_coords) { start = Location({start_index, parse_coordinates(json_vehicle, "start")}); } else { @@ -389,9 +400,9 @@ inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, // Check what info are available for vehicle end, then build // optional end location. - bool has_end_coords = json_vehicle.HasMember("end"); - bool has_end_index = json_vehicle.HasMember("end_index"); - if (has_end_index && !json_vehicle["end_index"].IsUint()) { + bool has_end_coords = json_vehicle.contains("end"); + bool has_end_index = json_vehicle.contains("end_index"); + if (has_end_index && !json_vehicle.at("end_index").is_number()) { throw InputException( std::format("Invalid end_index for vehicle {}.", v_id)); } @@ -399,7 +410,7 @@ inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, std::optional end; if (has_end_index) { // Custom provided matrices and index. - Index end_index = json_vehicle["end_index"].GetUint(); + Index end_index = json_vehicle.at("end_index").to_number(); if (has_end_coords) { end = Location({end_index, parse_coordinates(json_vehicle, "end")}); } else { @@ -433,20 +444,20 @@ inline Vehicle get_vehicle(const rapidjson::Value& json_vehicle, get_vehicle_steps(json_vehicle)); } -inline Location get_task_location(const rapidjson::Value& v, +inline Location get_task_location(const boost::json::object& v, const std::string& type) { // Check what info are available to build task location. - bool has_location_coords = v.HasMember("location"); - bool has_location_index = v.HasMember("location_index"); - if (has_location_index && !v["location_index"].IsUint()) { + bool has_location_coords = v.contains("location"); + bool has_location_index = v.contains("location_index"); + if (has_location_index && !v.at("location_index").is_number()) { throw InputException(std::format("Invalid location_index for {} {}.", type, - v["id"].GetUint64())); + v.at("id").to_number())); } if (has_location_index) { // Custom provided matrices and index. - Index location_index = v["location_index"].GetUint(); + Index location_index = v.at("location_index").to_number(); if (has_location_coords) { return Location({location_index, parse_coordinates(v, "location")}); } @@ -456,17 +467,17 @@ inline Location get_task_location(const rapidjson::Value& v, return Location(parse_coordinates(v, "location")); } -inline Job get_job(const rapidjson::Value& json_job, unsigned amount_size) { +inline Job get_job(const boost::json::object& json_job, unsigned amount_size) { check_id(json_job, "job"); // Only for retro-compatibility: when no pickup and delivery keys // are defined and (deprecated) amount key is present, it should be // interpreted as a delivery. - bool need_amount_compat = json_job.HasMember("amount") && - !json_job.HasMember("delivery") && - !json_job.HasMember("pickup"); + bool need_amount_compat = json_job.contains("amount") && + !json_job.contains("delivery") && + !json_job.contains("pickup"); - return Job(json_job["id"].GetUint64(), + return Job(json_job.at("id").to_number(), get_task_location(json_job, "job"), get_duration(json_job, "setup"), get_duration(json_job, "service"), @@ -479,24 +490,24 @@ inline Job get_job(const rapidjson::Value& json_job, unsigned amount_size) { get_string(json_job, "description")); } -template inline Matrix get_matrix(rapidjson::Value& m) { - if (!m.IsArray()) { +template inline Matrix get_matrix(boost::json::value& m) { + if (!m.is_array()) { throw InputException("Invalid matrix."); } // Load custom matrix while checking if it is square. - rapidjson::SizeType matrix_size = m.Size(); + size_t matrix_size = m.get_array().size(); Matrix matrix(matrix_size); - for (rapidjson::SizeType i = 0; i < matrix_size; ++i) { - if (!m[i].IsArray() || m[i].Size() != matrix_size) { + for (size_t i = 0; i < matrix_size; ++i) { + if (!m.at(i).is_array() || m.at(i).get_array().size() != matrix_size) { throw InputException("Unexpected matrix line length."); } - rapidjson::Document::Array mi = m[i].GetArray(); - for (rapidjson::SizeType j = 0; j < matrix_size; ++j) { - if (!mi[j].IsUint()) { + boost::json::array mi = m.at(i).get_array(); + for (size_t j = 0; j < matrix_size; ++j) { + if (!mi[j].is_number()) { throw InputException("Invalid matrix entry."); } - matrix[i][j] = mi[j].GetUint(); + matrix[i][j] = mi[j].to_number(); } } @@ -505,45 +516,54 @@ template inline Matrix get_matrix(rapidjson::Value& m) { void parse(Input& input, const std::string& input_str, bool geometry) { // Input json object. - rapidjson::Document json_input; + boost::json::error_code ec; + boost::json::monotonic_resource mr; + boost::json::parse_options opt; + opt.numbers = boost::json::number_precision::precise; + auto const content = boost::json::parse(input_str, ec, &mr, opt); - // Parsing input string to populate the input object. - if (json_input.Parse(input_str.c_str()).HasParseError()) { + if (ec) { std::string error_msg = - std::format("{} (offset: {})", - rapidjson::GetParseError_En(json_input.GetParseError()), - json_input.GetErrorOffset()); + std::format("{} (offset: {})", ec.message(), ec.location().to_string()); throw InputException(error_msg); } + if (!content.is_object()) + throw InputException("Invalid input."); + boost::json::object json_input = content.get_object(); + // Main checks for valid json input. - bool has_jobs = json_input.HasMember("jobs") && - json_input["jobs"].IsArray() && !json_input["jobs"].Empty(); - bool has_shipments = json_input.HasMember("shipments") && - json_input["shipments"].IsArray() && - !json_input["shipments"].Empty(); + bool has_jobs = json_input.contains("jobs") && + json_input.at("jobs").is_array() && + !json_input.at("jobs").get_array().empty(); + bool has_shipments = json_input.contains("shipments") && + json_input.at("shipments").is_array() && + !json_input.at("shipments").get_array().empty(); if (!has_jobs && !has_shipments) { throw InputException("Invalid jobs or shipments."); } - if (!json_input.HasMember("vehicles") || !json_input["vehicles"].IsArray() || - json_input["vehicles"].Empty()) { + if (!json_input.contains("vehicles") || + !json_input.at("vehicles").is_array() || + json_input.at("vehicles").get_array().empty()) { throw InputException("Invalid vehicles."); } - const auto& first_vehicle = json_input["vehicles"][0]; + const auto& first_vehicle = json_input.at("vehicles").at(0).get_object(); check_id(first_vehicle, "vehicle"); - bool first_vehicle_has_capacity = (first_vehicle.HasMember("capacity") && - first_vehicle["capacity"].IsArray() && - first_vehicle["capacity"].Size() > 0); + bool first_vehicle_has_capacity = + (first_vehicle.contains("capacity") && + first_vehicle.at("capacity").is_array() && + first_vehicle.at("capacity").get_array().size() > 0); const unsigned amount_size = - first_vehicle_has_capacity ? first_vehicle["capacity"].Size() : 0; + first_vehicle_has_capacity ? first_vehicle.at("capacity").get_array().size() + : 0; input.set_amount_size(amount_size); input.set_geometry(geometry); // Add all vehicles. - for (rapidjson::SizeType i = 0; i < json_input["vehicles"].Size(); ++i) { - auto& json_vehicle = json_input["vehicles"][i]; + for (size_t i = 0; i < json_input.at("vehicles").get_array().size(); ++i) { + auto& json_vehicle = json_input.at("vehicles").at(i).get_object(); input.add_vehicle(get_vehicle(json_vehicle, amount_size)); } @@ -551,15 +571,16 @@ void parse(Input& input, const std::string& input_str, bool geometry) { // Add all tasks. if (has_jobs) { // Add the jobs. - for (rapidjson::SizeType i = 0; i < json_input["jobs"].Size(); ++i) { - input.add_job(get_job(json_input["jobs"][i], amount_size)); + for (size_t i = 0; i < json_input.at("jobs").get_array().size(); ++i) { + input.add_job( + get_job(json_input.at("jobs").at(i).get_object(), amount_size)); } } if (has_shipments) { // Add the shipments. - for (rapidjson::SizeType i = 0; i < json_input["shipments"].Size(); ++i) { - auto& json_shipment = json_input["shipments"][i]; + for (size_t i = 0; i < json_input.at("shipments").get_array().size(); ++i) { + auto& json_shipment = json_input.at("shipments").at(i).get_object(); check_shipment(json_shipment); // Retrieve common stuff for both pickup and delivery. @@ -568,10 +589,10 @@ void parse(Input& input, const std::string& input_str, bool geometry) { auto priority = get_priority(json_shipment); // Defining pickup job. - auto& json_pickup = json_shipment["pickup"]; + auto& json_pickup = json_shipment.at("pickup").get_object(); check_id(json_pickup, "pickup"); - Job pickup(json_pickup["id"].GetUint64(), + Job pickup(json_pickup.at("id").to_number(), JOB_TYPE::PICKUP, get_task_location(json_pickup, "pickup"), get_duration(json_pickup, "setup"), @@ -583,10 +604,10 @@ void parse(Input& input, const std::string& input_str, bool geometry) { get_string(json_pickup, "description")); // Defining delivery job. - auto& json_delivery = json_shipment["delivery"]; + auto& json_delivery = json_shipment.at("delivery").get_object(); check_id(json_delivery, "delivery"); - Job delivery(json_delivery["id"].GetUint64(), + Job delivery(json_delivery.at("id").to_number(), JOB_TYPE::DELIVERY, get_task_location(json_delivery, "delivery"), get_duration(json_delivery, "setup"), @@ -601,36 +622,36 @@ void parse(Input& input, const std::string& input_str, bool geometry) { } } - if (json_input.HasMember("matrices")) { - if (!json_input["matrices"].IsObject()) { + if (json_input.contains("matrices")) { + if (!json_input.at("matrices").is_object()) { throw InputException("Unexpected matrices value."); } - for (auto& profile_entry : json_input["matrices"].GetObject()) { - if (profile_entry.value.IsObject()) { - if (profile_entry.value.HasMember("durations")) { - input.set_durations_matrix(profile_entry.name.GetString(), + for (auto& profile_entry : json_input.at("matrices").get_object()) { + if (profile_entry.value().is_object()) { + if (profile_entry.value().get_object().contains("durations")) { + input.set_durations_matrix(profile_entry.key(), get_matrix( - profile_entry.value["durations"])); + profile_entry.value().at("durations"))); } - if (profile_entry.value.HasMember("distances")) { - input.set_distances_matrix(profile_entry.name.GetString(), + if (profile_entry.value().get_object().contains("distances")) { + input.set_distances_matrix(profile_entry.key(), get_matrix( - profile_entry.value["distances"])); + profile_entry.value().at("distances"))); } - if (profile_entry.value.HasMember("costs")) { - input.set_costs_matrix(profile_entry.name.GetString(), + if (profile_entry.value().get_object().contains("costs")) { + input.set_costs_matrix(profile_entry.key(), get_matrix( - profile_entry.value["costs"])); + profile_entry.value().at("costs"))); } } } } else { // Deprecated `matrix` key still interpreted as // `matrices.DEFAULT_PROFILE.duration` for retro-compatibility. - if (json_input.HasMember("matrix")) { + if (json_input.contains("matrix")) { input.set_durations_matrix(DEFAULT_PROFILE, get_matrix( - json_input["matrix"])); + json_input.at("matrix"))); } } } diff --git a/src/utils/output_json.cpp b/src/utils/output_json.cpp index 4c03dbfd7..63352d848 100644 --- a/src/utils/output_json.cpp +++ b/src/utils/output_json.cpp @@ -10,30 +10,25 @@ All rights reserved (see LICENSE). #include #include -#include "../include/rapidjson/include/rapidjson/stringbuffer.h" -#include "../include/rapidjson/include/rapidjson/writer.h" - #include "utils/output_json.h" namespace vroom::io { -inline rapidjson::Value -get_violations(const Violations& violations, - rapidjson::Document::AllocatorType& allocator) { - rapidjson::Value json_violations(rapidjson::kArrayType); +inline boost::json::value get_violations(const Violations& violations) { + boost::json::array json_violations; for (const auto type : violations.types) { - rapidjson::Value json_violation(rapidjson::kObjectType); + boost::json::object json_violation; std::string cause; switch (type) { using enum VIOLATION; case LEAD_TIME: cause = "lead_time"; - json_violation.AddMember("duration", violations.lead_time, allocator); + json_violation["duration"] = violations.lead_time; break; case DELAY: cause = "delay"; - json_violation.AddMember("duration", violations.delay, allocator); + json_violation["duration"] = violations.delay; break; case LOAD: cause = "load"; @@ -63,38 +58,29 @@ get_violations(const Violations& violations, assert(false); } - json_violation.AddMember("cause", rapidjson::Value(), allocator); - json_violation["cause"].SetString(cause.c_str(), cause.size(), allocator); - - json_violations.PushBack(json_violation, allocator); + json_violation["cause"] = cause; + json_violations.emplace_back(json_violation); } return json_violations; } -rapidjson::Document to_json(const Solution& sol, bool report_distances) { - rapidjson::Document json_output; - json_output.SetObject(); - rapidjson::Document::AllocatorType& allocator = json_output.GetAllocator(); +boost::json::object to_json(const Solution& sol, bool report_distances) { + boost::json::object json_output; - json_output.AddMember("code", 0, allocator); - json_output.AddMember("summary", - to_json(sol.summary, report_distances, allocator), - allocator); + json_output["code"] = 0; + json_output["summary"] = to_json(sol.summary, report_distances); - rapidjson::Value json_unassigned(rapidjson::kArrayType); + boost::json::array json_unassigned; for (const auto& job : sol.unassigned) { - rapidjson::Value json_job(rapidjson::kObjectType); - json_job.AddMember("id", job.id, allocator); + boost::json::object json_job; + json_job["id"] = job.id; if (job.location.has_coordinates()) { - json_job.AddMember("location", - to_json(job.location, allocator), - allocator); + json_job["location"] = to_json(job.location); } if (job.location.user_index()) { - json_job.AddMember("location_index", job.location.index(), allocator); + json_job["location_index"] = job.location.index(); } - json_job.AddMember("type", rapidjson::Value(), allocator); std::string str_type; switch (job.type) { using enum JOB_TYPE; @@ -108,181 +94,149 @@ rapidjson::Document to_json(const Solution& sol, bool report_distances) { str_type = "delivery"; break; } - json_job["type"].SetString(str_type.c_str(), str_type.size(), allocator); + json_job["type"] = str_type; if (!job.description.empty()) { - json_job.AddMember("description", rapidjson::Value(), allocator); - json_job["description"].SetString(job.description.c_str(), - job.description.size(), - allocator); + json_job["description"] = job.description; } - json_unassigned.PushBack(json_job, allocator); + json_unassigned.emplace_back(json_job); } - json_output.AddMember("unassigned", json_unassigned, allocator); + json_output["unassigned"] = json_unassigned; - rapidjson::Value json_routes(rapidjson::kArrayType); + boost::json::array json_routes; for (const auto& route : sol.routes) { - json_routes.PushBack(to_json(route, report_distances, allocator), - allocator); + json_routes.emplace_back(to_json(route, report_distances)); } - json_output.AddMember("routes", json_routes, allocator); - + json_output["routes"] = json_routes; return json_output; } -rapidjson::Document to_json(const vroom::Exception& e) { - rapidjson::Document json_output; - json_output.SetObject(); - rapidjson::Document::AllocatorType& allocator = json_output.GetAllocator(); - - json_output.AddMember("code", e.error_code, allocator); - json_output.AddMember("error", rapidjson::Value(), allocator); - json_output["error"].SetString(e.message.c_str(), e.message.size()); +boost::json::object to_json(const vroom::Exception& e) { + boost::json::object json_output; + json_output["code"] = e.error_code; + json_output["error"] = e.message; return json_output; } -rapidjson::Value to_json(const Summary& summary, - bool report_distances, - rapidjson::Document::AllocatorType& allocator) { - rapidjson::Value json_summary(rapidjson::kObjectType); +boost::json::object to_json(const Summary& summary, bool report_distances) { + boost::json::object json_summary; - json_summary.AddMember("cost", summary.cost, allocator); - json_summary.AddMember("routes", summary.routes, allocator); - json_summary.AddMember("unassigned", summary.unassigned, allocator); + json_summary["cost"] = summary.cost; + json_summary["routes"] = summary.routes; + json_summary["unassigned"] = summary.unassigned; if (!summary.delivery.empty()) { - rapidjson::Value json_delivery(rapidjson::kArrayType); + boost::json::array json_delivery; for (std::size_t i = 0; i < summary.delivery.size(); ++i) { - json_delivery.PushBack(summary.delivery[i], allocator); + json_delivery.emplace_back(summary.delivery[i]); } - json_summary.AddMember("delivery", json_delivery, allocator); + json_summary["delivery"] = json_delivery; // Support for deprecated "amount" key. - rapidjson::Value json_amount(rapidjson::kArrayType); - for (std::size_t i = 0; i < summary.delivery.size(); ++i) { - json_amount.PushBack(summary.delivery[i], allocator); - } - json_summary.AddMember("amount", json_amount, allocator); + json_summary["amount"] = json_delivery; } if (!summary.pickup.empty()) { - rapidjson::Value json_pickup(rapidjson::kArrayType); + boost::json::array json_pickup; for (std::size_t i = 0; i < summary.pickup.size(); ++i) { - json_pickup.PushBack(summary.pickup[i], allocator); + json_pickup.emplace_back(summary.pickup[i]); } - json_summary.AddMember("pickup", json_pickup, allocator); + json_summary["pickup"] = json_pickup; } - json_summary.AddMember("setup", summary.setup, allocator); - json_summary.AddMember("service", summary.service, allocator); - json_summary.AddMember("duration", summary.duration, allocator); - json_summary.AddMember("waiting_time", summary.waiting_time, allocator); - json_summary.AddMember("priority", summary.priority, allocator); + json_summary["setup"] = summary.setup; + json_summary["service"] = summary.service; + json_summary["duration"] = summary.duration; + json_summary["waiting_time"] = summary.waiting_time; + json_summary["priority"] = summary.priority; if (report_distances) { - json_summary.AddMember("distance", summary.distance, allocator); + json_summary["distance"] = summary.distance; } - json_summary.AddMember("violations", - get_violations(summary.violations, allocator), - allocator); + json_summary["violations"] = get_violations(summary.violations); - json_summary.AddMember("computing_times", - to_json(summary.computing_times, allocator), - allocator); + json_summary["computing_times"] = to_json(summary.computing_times); return json_summary; } -rapidjson::Value to_json(const Route& route, - bool report_distances, - rapidjson::Document::AllocatorType& allocator) { - rapidjson::Value json_route(rapidjson::kObjectType); +boost::json::object to_json(const Route& route, bool report_distances) { + boost::json::object json_route; - json_route.AddMember("vehicle", route.vehicle, allocator); - json_route.AddMember("cost", route.cost, allocator); + json_route["vehicle"] = route.vehicle; + json_route["cost"] = route.cost; if (!route.description.empty()) { - json_route.AddMember("description", rapidjson::Value(), allocator); - json_route["description"].SetString(route.description.c_str(), - route.description.size(), - allocator); + json_route["description"] = route.description; } if (!route.delivery.empty()) { - rapidjson::Value json_delivery(rapidjson::kArrayType); + boost::json::array json_delivery; for (std::size_t i = 0; i < route.delivery.size(); ++i) { - json_delivery.PushBack(route.delivery[i], allocator); + json_delivery.emplace_back(route.delivery[i]); } - json_route.AddMember("delivery", json_delivery, allocator); + json_route["delivery"] = json_delivery; // Support for deprecated "amount" key. - rapidjson::Value json_amount(rapidjson::kArrayType); + boost::json::array json_amount; for (std::size_t i = 0; i < route.delivery.size(); ++i) { - json_amount.PushBack(route.delivery[i], allocator); + json_amount.emplace_back(route.delivery[i]); } - json_route.AddMember("amount", json_amount, allocator); + json_route["amount"] = json_amount; } if (!route.pickup.empty()) { - rapidjson::Value json_pickup(rapidjson::kArrayType); + boost::json::array json_pickup; for (std::size_t i = 0; i < route.pickup.size(); ++i) { - json_pickup.PushBack(route.pickup[i], allocator); + json_pickup.emplace_back(route.pickup[i]); } - json_route.AddMember("pickup", json_pickup, allocator); + json_route["pickup"] = json_pickup; } - json_route.AddMember("setup", route.setup, allocator); - json_route.AddMember("service", route.service, allocator); - json_route.AddMember("duration", route.duration, allocator); - json_route.AddMember("waiting_time", route.waiting_time, allocator); - json_route.AddMember("priority", route.priority, allocator); + json_route["setup"] = route.setup; + json_route["service"] = route.service; + json_route["duration"] = route.duration; + json_route["waiting_time"] = route.waiting_time; + json_route["priority"] = route.priority; if (report_distances) { - json_route.AddMember("distance", route.distance, allocator); + json_route["distance"] = route.distance; } - rapidjson::Value json_steps(rapidjson::kArrayType); + boost::json::array json_steps; for (const auto& step : route.steps) { - json_steps.PushBack(to_json(step, report_distances, allocator), allocator); + json_steps.emplace_back(to_json(step, report_distances)); } - json_route.AddMember("steps", json_steps, allocator); + json_route["steps"] = json_steps; - json_route.AddMember("violations", - get_violations(route.violations, allocator), - allocator); + json_route["violations"] = get_violations(route.violations); if (!route.geometry.empty()) { - json_route.AddMember("geometry", rapidjson::Value(), allocator); - json_route["geometry"].SetString(route.geometry.c_str(), - route.geometry.size()); + json_route["geometry"] = route.geometry; } return json_route; } -rapidjson::Value to_json(const ComputingTimes& ct, - rapidjson::Document::AllocatorType& allocator) { - rapidjson::Value json_ct(rapidjson::kObjectType); +boost::json::object to_json(const ComputingTimes& ct) { + boost::json::object json_ct; - json_ct.AddMember("loading", ct.loading, allocator); - json_ct.AddMember("solving", ct.solving, allocator); - json_ct.AddMember("routing", ct.routing, allocator); + json_ct["loading"] = ct.loading; + json_ct["solving"] = ct.solving; + json_ct["routing"] = ct.routing; return json_ct; } -rapidjson::Value to_json(const Step& s, - bool report_distances, - rapidjson::Document::AllocatorType& allocator) { - rapidjson::Value json_step(rapidjson::kObjectType); +boost::json::object to_json(const Step& s, bool report_distances) { + boost::json::object json_step; - json_step.AddMember("type", rapidjson::Value(), allocator); std::string str_type; switch (s.step_type) { using enum STEP_TYPE; @@ -312,86 +266,75 @@ rapidjson::Value to_json(const Step& s, break; } } - json_step["type"].SetString(str_type.c_str(), str_type.size(), allocator); + json_step["type"] = str_type; if (!s.description.empty()) { - json_step.AddMember("description", rapidjson::Value(), allocator); - json_step["description"].SetString(s.description.c_str(), - s.description.size(), - allocator); + json_step["description"] = s.description; } if (s.location.has_value()) { const auto& loc = s.location.value(); if (loc.has_coordinates()) { - json_step.AddMember("location", to_json(loc, allocator), allocator); + json_step["location"] = to_json(loc); } if (loc.user_index()) { - json_step.AddMember("location_index", loc.index(), allocator); + json_step["location_index"] = loc.index(); } } if (s.step_type == STEP_TYPE::JOB || s.step_type == STEP_TYPE::BREAK) { - json_step.AddMember("id", s.id, allocator); + json_step["id"] = s.id; } - json_step.AddMember("setup", s.setup, allocator); - json_step.AddMember("service", s.service, allocator); - json_step.AddMember("waiting_time", s.waiting_time, allocator); + json_step["setup"] = s.setup; + json_step["service"] = s.service; + json_step["waiting_time"] = s.waiting_time; // Should be removed at some point as step.job is deprecated. if (s.step_type == STEP_TYPE::JOB) { - json_step.AddMember("job", s.id, allocator); + json_step["job"] = s.id; } if (!s.load.empty()) { - rapidjson::Value json_load(rapidjson::kArrayType); + boost::json::array json_load; for (std::size_t i = 0; i < s.load.size(); ++i) { - json_load.PushBack(s.load[i], allocator); + json_load.emplace_back(s.load[i]); } - json_step.AddMember("load", json_load, allocator); + json_step["load"] = json_load; } - json_step.AddMember("arrival", s.arrival, allocator); - json_step.AddMember("duration", s.duration, allocator); - - json_step.AddMember("violations", - get_violations(s.violations, allocator), - allocator); + json_step["arrival"] = s.arrival; + json_step["duration"] = s.duration; + json_step["violations"] = get_violations(s.violations); if (report_distances) { - json_step.AddMember("distance", s.distance, allocator); + json_step["distance"] = s.distance; } return json_step; } -rapidjson::Value to_json(const Location& loc, - rapidjson::Document::AllocatorType& allocator) { - rapidjson::Value json_coords(rapidjson::kArrayType); +boost::json::array to_json(const Location& loc) { + boost::json::array json_coords; - json_coords.PushBack(loc.lon(), allocator); - json_coords.PushBack(loc.lat(), allocator); + json_coords.emplace_back(loc.lon()); + json_coords.emplace_back(loc.lat()); return json_coords; } -void write_to_output(const rapidjson::Document& json_output, +void write_to_output(const boost::json::value& json_output, const std::string& output_file) { - // Rapidjson writing process. - rapidjson::StringBuffer s; - rapidjson::Writer r_writer(s); - json_output.Accept(r_writer); // Write to relevant output. if (output_file.empty()) { // Log to standard output. - std::cout << s.GetString() << std::endl; + std::cout << boost::json::serialize(json_output) << std::endl; } else { // Log to file. std::ofstream out_stream(output_file, std::ofstream::out); - out_stream << s.GetString(); + out_stream << boost::json::serialize(json_output); out_stream.close(); } } diff --git a/src/utils/output_json.h b/src/utils/output_json.h index 6daf53f02..531f8913c 100644 --- a/src/utils/output_json.h +++ b/src/utils/output_json.h @@ -10,33 +10,26 @@ All rights reserved (see LICENSE). */ -#include "../include/rapidjson/include/rapidjson/document.h" +#include + #include "structures/vroom/solution/solution.h" #include "utils/exception.h" namespace vroom::io { -rapidjson::Document to_json(const Solution& sol, bool report_distances); +boost::json::object to_json(const Solution& sol, bool report_distances); -rapidjson::Document to_json(const vroom::Exception& e); +boost::json::object to_json(const vroom::Exception& e); -rapidjson::Value to_json(const Summary& summary, - bool report_distances, - rapidjson::Document::AllocatorType& allocator); +boost::json::object to_json(const Summary& summary, bool report_distances); -rapidjson::Value to_json(const ComputingTimes& ct, - rapidjson::Document::AllocatorType& allocator); +boost::json::object to_json(const ComputingTimes& ct); -rapidjson::Value to_json(const Route& route, - bool report_distances, - rapidjson::Document::AllocatorType& allocator); +boost::json::object to_json(const Route& route, bool report_distances); -rapidjson::Value to_json(const Step& s, - bool report_distances, - rapidjson::Document::AllocatorType& allocator); +boost::json::object to_json(const Step& s, bool report_distances); -rapidjson::Value to_json(const Location& loc, - rapidjson::Document::AllocatorType& allocator); +boost::json::array to_json(const Location& loc); void write_to_json(const vroom::Exception& e, const std::string& output_file = "");