diff --git a/CMake/json-download.cmake.in b/CMake/json-download.cmake.in index 5fd3e71fbd5..2b719edd4b4 100644 --- a/CMake/json-download.cmake.in +++ b/CMake/json-download.cmake.in @@ -6,7 +6,7 @@ ExternalProject_Add( nlohmann_json PREFIX . GIT_REPOSITORY "https://github.com/nlohmann/json.git" - #GIT_TAG v3.11.2 + GIT_TAG v3.11.3 GIT_CONFIG advice.detachedHead=false # otherwise we'll get "You are in 'detached HEAD' state..." SOURCE_DIR "${CMAKE_BINARY_DIR}/third-party/json" GIT_SHALLOW 1 # No history needed (requires cmake 3.6) diff --git a/common/device-model.cpp b/common/device-model.cpp index 3b9e3d35b5a..134e87ae68b 100644 --- a/common/device-model.cpp +++ b/common/device-model.cpp @@ -22,7 +22,7 @@ #include "device-model.h" using namespace rs400; -using namespace nlohmann; +using rsutils::json; using namespace rs2::sw_update; namespace rs2 diff --git a/common/device-model.h b/common/device-model.h index bd7f0f69c62..20e434ea36c 100644 --- a/common/device-model.h +++ b/common/device-model.h @@ -1,12 +1,11 @@ // License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2023 Intel Corporation. All Rights Reserved. - #pragma once #include #include "notifications.h" #include "realsense-ui-advanced-mode.h" -#include +#include #include "sw-update/dev-updates-profile.h" #include #include "updates-model.h" @@ -426,7 +425,7 @@ namespace rs2 const std::string& error_message); void load_viewer_configurations(const std::string& json_str); - void save_viewer_configurations(std::ofstream& outfile, nlohmann::json& j); + void save_viewer_configurations(std::ofstream& outfile, rsutils::json& j); void handle_online_sw_update( std::shared_ptr< notifications_model > nm, std::shared_ptr< sw_update::dev_updates_profile::update_profile > update_profile, diff --git a/common/rs-config.cpp b/common/rs-config.cpp index ad443c0038a..81443816c2a 100644 --- a/common/rs-config.cpp +++ b/common/rs-config.cpp @@ -8,7 +8,7 @@ #include #include -using json = nlohmann::json; +using json = rsutils::json; using namespace rs2; @@ -31,7 +31,7 @@ void config_file::remove(const char* key) void config_file::reset() { - _j = nlohmann::json::object(); + _j = json::object(); save(); } @@ -40,7 +40,7 @@ std::string config_file::get(const char* key, const char* def) const auto it = _j.find(key); if (it != _j.end() && it->is_string()) { - return rsutils::json::string_ref( *it ); + return it->string_ref(); } return get_default(key, def); } @@ -105,7 +105,7 @@ void config_file::save() } config_file::config_file() - : _j( nlohmann::json::object() ) + : _j( rsutils::json::object() ) { } diff --git a/common/rs-config.h b/common/rs-config.h index 9165e032395..9bf4e96cc84 100644 --- a/common/rs-config.h +++ b/common/rs-config.h @@ -2,7 +2,7 @@ // Copyright(c) 2017 Intel Corporation. All Rights Reserved. #pragma once -#include +#include #include #include @@ -94,6 +94,6 @@ namespace rs2 std::map _defaults; std::string _filename; - nlohmann::json _j; + rsutils::json _j; }; } \ No newline at end of file diff --git a/common/subdevice-model.h b/common/subdevice-model.h index a22f2420200..9713bd73b30 100644 --- a/common/subdevice-model.h +++ b/common/subdevice-model.h @@ -18,7 +18,6 @@ #include #include -#include #include "objects-in-frame.h" #include "processing-block-model.h" diff --git a/common/sw-update/versions-db-manager.cpp b/common/sw-update/versions-db-manager.cpp index 7de2473a507..514f4c25806 100644 --- a/common/sw-update/versions-db-manager.cpp +++ b/common/sw-update/versions-db-manager.cpp @@ -2,7 +2,7 @@ // Copyright(c) 2020 Intel Corporation. All Rights Reserved. #include "versions-db-manager.h" -#include +#include #include #include #include @@ -16,7 +16,7 @@ namespace rs2 namespace sw_update { - using json = nlohmann::json; + using json = rsutils::json; using namespace http; query_status_type versions_db_manager::query_versions(const std::string &device_name, component_part_type component, const update_policy_type policy, version& out_version) diff --git a/src/context.cpp b/src/context.cpp index 5312515efd8..db857670703 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -19,48 +19,34 @@ #include #include #include -using json = nlohmann::json; - -#include +#include +using json = rsutils::json; namespace librealsense { - static nlohmann::json load_settings( nlohmann::json const & context_settings ) + static rsutils::json load_settings( rsutils::json const & context_settings ) { // Allow ignoring of any other settings, global or not! - if( ! rsutils::json::get( context_settings, "inherit", true ) ) + if( ! context_settings.nested( "inherit" ).default_value( true ) ) return context_settings; - nlohmann::json config; - - // Load the realsense configuration file settings auto const filename = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + RS2_CONFIG_FILENAME; - std::ifstream f( filename ); - if( f.good() ) - { - try - { - config = nlohmann::json::parse( f ); - } - catch( std::exception const & e ) - { - throw std::runtime_error( "failed to load configuration file (" + filename + "): " + std::string( e.what() ) ); - } - } + auto config = rsutils::json_config::load_from_file( filename ); - config = rsutils::json::load_settings( config, "context", "config-file" ); + // Take only the 'context' part of it + config = rsutils::json_config::load_settings( config, "context", "config-file" ); // Patch the given context settings into the configuration - rsutils::json::patch( config, context_settings, "context settings" ); + config.override( context_settings, "context settings" ); return config; } context::context( json const & settings ) : _settings( load_settings( settings ) ) // global | application | local - , _device_mask( rsutils::json::get< unsigned >( _settings, "device-mask", RS2_PRODUCT_LINE_ANY ) ) + , _device_mask( _settings.nested( "device-mask" ).default_value< unsigned >( RS2_PRODUCT_LINE_ANY ) ) { static bool version_logged = false; if( ! version_logged ) @@ -192,7 +178,7 @@ namespace librealsense { std::shared_ptr< processing_block_interface > context::create_pp_block( std::string const & name, - nlohmann::json const & settings ) + rsutils::json const & settings ) { return rscore_pp_block_factory().create_pp_block( name, settings ); } diff --git a/src/context.h b/src/context.h index c33792791e5..807bcea842a 100644 --- a/src/context.h +++ b/src/context.h @@ -17,12 +17,12 @@ namespace librealsense class context { - context( nlohmann::json const & ); // private! use make() + context( rsutils::json const & ); // private! use make() void create_factories( std::shared_ptr< context > const & sptr ); public: - static std::shared_ptr< context > make( nlohmann::json const & ); + static std::shared_ptr< context > make( rsutils::json const & ); static std::shared_ptr< context > make( char const * json_settings ); ~context(); @@ -65,7 +65,7 @@ namespace librealsense // Create processing blocks given a name and settings. // std::shared_ptr< processing_block_interface > create_pp_block( std::string const & name, - nlohmann::json const & settings ); + rsutils::json const & settings ); private: void invoke_devices_changed_callbacks( std::vector< std::shared_ptr< device_info > > const & devices_removed, diff --git a/src/core/pp-block-factory.h b/src/core/pp-block-factory.h index 85a76f6273a..d41845b2681 100644 --- a/src/core/pp-block-factory.h +++ b/src/core/pp-block-factory.h @@ -2,7 +2,7 @@ // Copyright(c) 2023 Intel Corporation. All Rights Reserved. #pragma once -#include +#include #include @@ -30,7 +30,7 @@ class pp_block_factory // The name is case-insensitive. // virtual std::shared_ptr< processing_block_interface > create_pp_block( std::string const & name, - nlohmann::json const & settings ) + rsutils::json const & settings ) = 0; }; diff --git a/src/dds/rs-dds-depth-sensor-proxy.cpp b/src/dds/rs-dds-depth-sensor-proxy.cpp index ea0667b0639..8992cf5e6f2 100644 --- a/src/dds/rs-dds-depth-sensor-proxy.cpp +++ b/src/dds/rs-dds-depth-sensor-proxy.cpp @@ -40,13 +40,13 @@ void dds_depth_sensor_proxy::add_no_metadata( frame * const f, streaming_impl & void dds_depth_sensor_proxy::add_frame_metadata( frame * const f, rsutils::json const & dds_md, streaming_impl & streaming ) { - if( auto du = dds_md.find( metadata_header_key, depth_units_key ) ) + if( auto du = dds_md.nested( metadata_header_key, depth_units_key ) ) { try { - f->additional_data.depth_units = du.value< float >(); + f->additional_data.depth_units = du.get< float >(); } - catch( nlohmann::json::exception const & ) + catch( rsutils::json::exception const & ) { f->additional_data.depth_units = get_depth_scale(); } diff --git a/src/dds/rs-dds-device-proxy.cpp b/src/dds/rs-dds-device-proxy.cpp index e46d140dd17..2698ab73f71 100644 --- a/src/dds/rs-dds-device-proxy.cpp +++ b/src/dds/rs-dds-device-proxy.cpp @@ -127,19 +127,19 @@ dds_device_proxy::dds_device_proxy( std::shared_ptr< const device_info > const & auto & j = dev->device_info().to_json(); std::string str; - if( rsutils::json::get_ex( j, "serial", &str ) ) + if( j.nested( "serial" ).get_ex( str ) ) { register_info( RS2_CAMERA_INFO_SERIAL_NUMBER, str ); - rsutils::json::get_ex( j, "fw-update-id", &str ); // if fails, str will be the serial + j.nested( "fw-update-id" ).get_ex( str ); // if fails, str will be the serial register_info( RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID, str ); } - else if( rsutils::json::get_ex( j, "fw-update-id", &str ) ) + else if( j.nested( "fw-update-id" ).get_ex( str ) ) register_info( RS2_CAMERA_INFO_FIRMWARE_UPDATE_ID, str ); - if( rsutils::json::get_ex( j, "fw-version", &str ) ) + if( j.nested( "fw-version" ).get_ex( str ) ) register_info( RS2_CAMERA_INFO_FIRMWARE_VERSION, str ); - if( rsutils::json::get_ex( j, "product-line", &str ) ) + if( j.nested( "product-line" ).get_ex( str ) ) register_info( RS2_CAMERA_INFO_PRODUCT_LINE, str ); - register_info( RS2_CAMERA_INFO_CAMERA_LOCKED, rsutils::json::get( j, "locked", true ) ? "YES" : "NO" ); + register_info( RS2_CAMERA_INFO_CAMERA_LOCKED, j.nested( "locked" ).default_value( true ) ? "YES" : "NO" ); // Assumes dds_device initialization finished struct sensor_info @@ -298,7 +298,7 @@ dds_device_proxy::dds_device_proxy( std::shared_ptr< const device_info > const & _metadata_subscription = _dds_dev->on_metadata_available( [this]( std::shared_ptr< const rsutils::json > const & dds_md ) { - std::string const & stream_name = rsutils::json::nested( *dds_md, stream_name_key ).string_ref(); + std::string const & stream_name = dds_md->nested( stream_name_key ).string_ref(); auto it = _stream_name_to_owning_sensor.find( stream_name ); if( it != _stream_name_to_owning_sensor.end() ) it->second->handle_new_metadata( stream_name, dds_md ); @@ -354,7 +354,7 @@ dds_device_proxy::dds_device_proxy( std::shared_ptr< const device_info > const & // Depth & IR matched by frame-number, time-stamp-matched to color. // Motion streams will not get synced. rs2_matchers matcher = RS2_MATCHER_DLR_C; - if( auto matcher_j = _dds_dev->participant()->settings().find( "device", "matcher" ) ) + if( auto matcher_j = _dds_dev->participant()->settings().nested( "device", "matcher" ) ) { if( ! matcher_j.is_string() || ! try_parse( matcher_j.string_ref(), matcher ) ) LOG_WARNING( "Invalid 'device/matcher' value " << matcher_j ); @@ -520,7 +520,7 @@ std::vector< uint8_t > dds_device_proxy::send_receive_raw_data( const std::vecto rsutils::json reply; _dds_dev->send_control( control, &reply ); rsutils::string::hexarray data; - if( ! rsutils::json::get_ex( reply, "data", &data ) ) + if( ! reply.nested( "data" ).get_ex( data ) ) throw std::runtime_error( "Failed HWM: missing 'data' in reply" ); return data.detach(); } @@ -546,7 +546,7 @@ std::vector< uint8_t > dds_device_proxy::build_command( uint32_t opcode, { "build-command", true } } ); rsutils::json reply; _dds_dev->send_control( control, &reply ); - if( ! rsutils::json::get_ex( reply, "data", &hexdata ) ) + if( ! reply.nested( "data" ).get_ex( hexdata ) ) throw std::runtime_error( "Failed HWM: missing 'data' in reply" ); return hexdata.detach(); } diff --git a/src/dds/rs-dds-sensor-proxy.cpp b/src/dds/rs-dds-sensor-proxy.cpp index 48000d0e669..35abf678340 100644 --- a/src/dds/rs-dds-sensor-proxy.cpp +++ b/src/dds/rs-dds-sensor-proxy.cpp @@ -346,8 +346,8 @@ void dds_sensor_proxy::handle_new_metadata( std::string const & stream_name, auto it = _streaming_by_name.find( stream_name ); if( it != _streaming_by_name.end() ) { - if( auto timestamp = rsutils::json::nested( *dds_md, metadata_header_key, timestamp_key ) ) - it->second.syncer.enqueue_metadata( timestamp.value< realdds::dds_nsec >(), dds_md ); + if( auto timestamp = dds_md->nested( metadata_header_key, timestamp_key ) ) + it->second.syncer.enqueue_metadata( timestamp.get< realdds::dds_nsec >(), dds_md ); else throw std::runtime_error( "missing metadata header/timestamp" ); } @@ -369,14 +369,14 @@ void dds_sensor_proxy::add_frame_metadata( frame * const f, rsutils::json const & dds_md, streaming_impl & streaming ) { - auto md_header = dds_md.find( metadata_header_key ); - auto md = dds_md.find( metadata_key ); + auto md_header = dds_md.nested( metadata_header_key ); + auto md = dds_md.nested( metadata_key ); // A frame number is "optional". If the server supplies it, we try to use it for the simple fact that, // otherwise, we have no way of detecting drops without some advanced heuristic tracking the FPS and // timestamps. If not supplied, we use an increasing counter. // Note that if we have no metadata, we have no frame-numbers! So we need a way of generating them - if( rsutils::json::get_ex( md_header, frame_number_key, &f->additional_data.frame_number ) ) + if( md_header.nested( frame_number_key ).get_ex( f->additional_data.frame_number ) ) { f->additional_data.last_frame_number = streaming.last_frame_number.exchange( f->additional_data.frame_number ); if( f->additional_data.frame_number != f->additional_data.last_frame_number + 1 @@ -396,7 +396,7 @@ void dds_sensor_proxy::add_frame_metadata( frame * const f, // purposes, so we ignore here. The domain is optional, and really only rs-dds-adapter communicates it // because the source is librealsense... f->additional_data.timestamp; - rsutils::json::get_ex( md_header, timestamp_domain_key, &f->additional_data.timestamp_domain ); + md_header.nested( timestamp_domain_key ).get_ex( f->additional_data.timestamp_domain ); if( ! md.empty() ) { @@ -408,11 +408,8 @@ void dds_sensor_proxy::add_frame_metadata( frame * const f, std::string const & keystr = librealsense::get_string( key ); try { - if( auto value_j = md.find( keystr ) ) - { - if( value_j.is_number_integer() ) - metadata[key] = { true, value_j.value< rs2_metadata_type >() }; - } + if( auto value_j = md.nested( keystr, &rsutils::json::is_number_integer ) ) + metadata[key] = { true, value_j.get< rs2_metadata_type >() }; } catch( rsutils::json::exception const & ) { diff --git a/src/dds/rsdds-device-factory.cpp b/src/dds/rsdds-device-factory.cpp index b5df8e9ae58..6c52989ebcd 100644 --- a/src/dds/rsdds-device-factory.cpp +++ b/src/dds/rsdds-device-factory.cpp @@ -83,12 +83,12 @@ static std::mutex domain_context_by_id_mutex; rsdds_device_factory::rsdds_device_factory( std::shared_ptr< context > const & ctx, callback && cb ) : super( ctx ) { - auto dds_settings = ctx->get_settings().find( std::string( "dds", 3 ) ); + auto dds_settings = ctx->get_settings().nested( std::string( "dds", 3 ) ); if( ! dds_settings.exists() - || dds_settings.is_object() && dds_settings.find( std::string( "enabled", 7 ) ).default_value( true ) ) + || dds_settings.is_object() && dds_settings.nested( std::string( "enabled", 7 ) ).default_value( true ) ) { - auto domain_id = dds_settings.find( std::string( "domain", 6 ) ).default_value< realdds::dds_domain_id >( 0 ); - auto participant_name_j = dds_settings.find( std::string( "participant", 11 ) ); + auto domain_id = dds_settings.nested( std::string( "domain", 6 ) ).default_value< realdds::dds_domain_id >( 0 ); + auto participant_name_j = dds_settings.nested( std::string( "participant", 11 ) ); auto participant_name = participant_name_j.default_value( rsutils::os::executable_name() ); std::lock_guard< std::mutex > lock( domain_context_by_id_mutex ); @@ -145,7 +145,7 @@ std::vector< std::shared_ptr< device_info > > rsdds_device_factory::query_device return true; } std::string product_line; - if( rsutils::json::get_ex( dev->device_info().to_json(), "product-line", &product_line ) ) + if( dev->device_info().to_json().nested( "product-line" ).get_ex( product_line ) ) { if( product_line == "D400" ) { diff --git a/src/device.cpp b/src/device.cpp index 33c4d3c8b9c..a9dd9bb48a4 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -204,7 +204,7 @@ format_conversion device::get_format_conversion() const return format_conversion::full; std::string const format_conversion( "format-conversion", 17 ); std::string const full( "full", 4 ); - auto const value = rsutils::json::get( context->get_settings(), format_conversion, full ); + auto const value = context->get_settings().nested( format_conversion ).default_value( full ); if( value == full ) return format_conversion::full; if( value == "basic" ) diff --git a/src/ds/advanced_mode/json_loader.hpp b/src/ds/advanced_mode/json_loader.hpp index a927ae9ac1d..dcf7ee932dc 100644 --- a/src/ds/advanced_mode/json_loader.hpp +++ b/src/ds/advanced_mode/json_loader.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include "types.h" #include "presets.h" @@ -23,7 +23,7 @@ namespace librealsense { - using json = nlohmann::json; + using json = rsutils::json; template struct param_group @@ -498,7 +498,7 @@ namespace librealsense { try { - if (value.type() != nlohmann::basic_json<>::value_t::string) + if( ! value.is_string() ) { float val = value; std::stringstream ss; diff --git a/src/ds/d400/d400-auto-calibration.cpp b/src/ds/d400/d400-auto-calibration.cpp index 9629bd6d979..61096a96fb1 100644 --- a/src/ds/d400/d400-auto-calibration.cpp +++ b/src/ds/d400/d400-auto-calibration.cpp @@ -2,7 +2,7 @@ // Copyright(c) 2016 Intel Corporation. All Rights Reserved. #include -#include +#include #include "d400-device.h" #include "d400-private.h" #include "d400-thermal-monitor.h" @@ -203,13 +203,11 @@ namespace librealsense std::map auto_calibrated::parse_json(std::string json_content) { - using json = nlohmann::json; - - auto j = json::parse(json_content); + auto j = rsutils::json::parse(json_content); std::map values; - for (json::iterator it = j.begin(); it != j.end(); ++it) + for (auto it = j.begin(); it != j.end(); ++it) { values[it.key()] = it.value(); } diff --git a/src/ds/d400/d400-device.cpp b/src/ds/d400/d400-device.cpp index bdf64596969..103088496d3 100644 --- a/src/ds/d400/d400-device.cpp +++ b/src/ds/d400/d400-device.cpp @@ -36,7 +36,6 @@ #include "d400-thermal-monitor.h" #include #include -#include #include #include diff --git a/src/ds/d500/d500-device.cpp b/src/ds/d500/d500-device.cpp index 7a071114838..e182d46967d 100644 --- a/src/ds/d500/d500-device.cpp +++ b/src/ds/d500/d500-device.cpp @@ -37,7 +37,6 @@ #include #include -#include #include #include diff --git a/src/rs.cpp b/src/rs.cpp index 80c3f0502ab..ca6e75f5f7c 100644 --- a/src/rs.cpp +++ b/src/rs.cpp @@ -186,7 +186,7 @@ rs2_context* rs2_create_context(int api_version, rs2_error** error) BEGIN_API_CA { verify_version_compatibility(api_version); - nlohmann::json settings; + rsutils::json settings; return new rs2_context{ context::make( settings ) }; } HANDLE_EXCEPTIONS_AND_RETURN(nullptr, api_version) @@ -2650,7 +2650,7 @@ NOARGS_HANDLE_EXCEPTIONS_AND_RETURN(0) rs2_device* rs2_create_software_device(rs2_error** error) BEGIN_API_CALL { // We're not given a context... - auto ctx = context::make( nlohmann::json::object( { { "dds", false } } ) ); + auto ctx = context::make( rsutils::json::object( { { "dds", false } } ) ); auto dev_info = std::make_shared< software_device_info >( ctx ); auto dev = std::make_shared< software_device >( dev_info ); dev_info->set_device( dev ); diff --git a/src/rscore-pp-block-factory.cpp b/src/rscore-pp-block-factory.cpp index df830fd1b0c..679bcf63e06 100644 --- a/src/rscore-pp-block-factory.cpp +++ b/src/rscore-pp-block-factory.cpp @@ -20,7 +20,7 @@ namespace librealsense { std::shared_ptr< processing_block_interface > -rscore_pp_block_factory::create_pp_block( std::string const & name, nlohmann::json const & settings ) +rscore_pp_block_factory::create_pp_block( std::string const & name, rsutils::json const & settings ) { // These filters do not accept settings (nor are settings recorded in ros_writer) (void *)&settings; diff --git a/src/rscore-pp-block-factory.h b/src/rscore-pp-block-factory.h index 4671d54ea24..58bc2819487 100644 --- a/src/rscore-pp-block-factory.h +++ b/src/rscore-pp-block-factory.h @@ -12,7 +12,7 @@ class rscore_pp_block_factory : public pp_block_factory { public: std::shared_ptr< processing_block_interface > create_pp_block( std::string const & name, - nlohmann::json const & settings ) override; + rsutils::json const & settings ) override; }; diff --git a/src/serialized-utilities.h b/src/serialized-utilities.h index 55d04fccf2f..20130cfaa96 100644 --- a/src/serialized-utilities.h +++ b/src/serialized-utilities.h @@ -1,11 +1,11 @@ // License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2022 Intel Corporation. All Rights Reserved. - #pragma once + #include #include #include -#include +#include namespace librealsense @@ -14,7 +14,7 @@ namespace librealsense namespace serialized_utilities { - using json = nlohmann::json; + using json = rsutils::json; struct device_info { diff --git a/third-party/realdds/include/realdds/dds-serialization.h b/third-party/realdds/include/realdds/dds-serialization.h index d760dfa6350..c4d75fcfc49 100644 --- a/third-party/realdds/include/realdds/dds-serialization.h +++ b/third-party/realdds/include/realdds/dds-serialization.h @@ -28,14 +28,17 @@ class DomainParticipantQos; namespace eprosima { namespace fastrtps { + // Allow j["key"] = qos.lease_duration; -void to_json( rsutils::json_type &, Duration_t const & ); +void to_json( rsutils::json &, Duration_t const & ); // Allow j.get< eprosima::fastrtps::Duration_t >(); -void from_json( rsutils::json_type const &, Duration_t & ); +void from_json( rsutils::json const &, Duration_t & ); + namespace rtps { std::ostream & operator<<( std::ostream &, class WriterProxyData const & ); std::ostream & operator<<( std::ostream &, class ReaderProxyData const & ); } // namespace rtps + } // namespace fastrtps } // namespace eprosima diff --git a/third-party/realdds/py/pyrealdds.cpp b/third-party/realdds/py/pyrealdds.cpp index aa0f5b10b28..d526ac5541f 100644 --- a/third-party/realdds/py/pyrealdds.cpp +++ b/third-party/realdds/py/pyrealdds.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -77,40 +78,28 @@ std::string script_name() rsutils::json load_rs_settings( rsutils::json const & local_settings ) { - rsutils::json config; - // Load the realsense configuration file settings - std::ifstream f( rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + "realsense-config.json" ); - if( f.good() ) - { - try - { - config = rsutils::json::parse( f ); - } - catch( std::exception const & e ) - { - throw std::runtime_error( "failed to load configuration file: " + std::string( e.what() ) ); - } - } + std::string const filename = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + "realsense-config.json"; + auto config = rsutils::json_config::load_from_file( filename ); // Load "python"-specific settings - auto settings = rsutils::json::load_app_settings( config, "python", "context", "config-file" ); + rsutils::json settings = rsutils::json_config::load_app_settings( config, "python", "context", "config-file" ); // Take the "dds" settings only - settings = rsutils::json::nested( settings, "dds" ); + settings = settings.nested( "dds" ); // Patch any script-specific settings // NOTE: this is also accessed by pyrealsense2, where a "context" hierarchy is still used auto script = script_name(); - if( auto script_settings = rsutils::json::nested( config, script, "context", "dds" ) ) - rsutils::json::patch( settings, script_settings, "config-file/" + script + "/context" ); + if( auto script_settings = config.nested( script, "context", "dds" ) ) + settings.override( script_settings, "config-file/" + script + "/context" ); // We should always have DDS enabled if( settings.is_object() ) settings.erase( "enabled" ); // Patch the given local settings into the configuration - rsutils::json::patch( settings, local_settings, "local settings" ); + settings.override( local_settings, "local settings" ); return settings; } diff --git a/third-party/realdds/src/dds-device-impl.cpp b/third-party/realdds/src/dds-device-impl.cpp index 76847a1619c..69011f7b751 100644 --- a/third-party/realdds/src/dds-device-impl.cpp +++ b/third-party/realdds/src/dds-device-impl.cpp @@ -49,8 +49,8 @@ namespace { rsutils::json device_settings( std::shared_ptr< realdds::dds_participant > const & participant ) { - rsutils::json settings = participant->settings().find( "device" ); - if( settings.is_null() ) + auto settings = participant->settings().nested( "device" ); + if( ! settings ) // Nothing there: default is empty object return rsutils::json::object(); if( ! settings.is_object() ) @@ -95,7 +95,7 @@ void dds_device::impl::set_state( state_t new_state ) { if( _metadata_reader ) { - auto md_settings = _device_settings.find( "metadata" ); + auto md_settings = _device_settings.nested( "metadata" ); if( md_settings.exists() && ! md_settings.is_object() ) // not found is null { LOG_DEBUG( "[" << debug_name() << "] ... metadata is available but device/metadata is disabled" ); @@ -137,7 +137,7 @@ dds_device::impl::impl( std::shared_ptr< dds_participant > const & participant, , _subscriber( std::make_shared< dds_subscriber >( participant ) ) , _device_settings( device_settings( participant ) ) , _reply_timeout_ms( - rsutils::json::nested( _device_settings, "control", "reply-timeout-ms" ).default_value< size_t >( 2000 ) ) + _device_settings.nested( "control", "reply-timeout-ms" ).default_value< size_t >( 2000 ) ) { create_notifications_reader(); create_control_writer(); @@ -178,7 +178,7 @@ void dds_device::impl::handle_notification( rsutils::json const & j, try { // First handle the notification - auto id = rsutils::json::get< std::string >( j, id_key ); + auto id = j.at( id_key ).get< std::string >(); auto it = _notification_handlers.find( id ); if( it != _notification_handlers.end() ) ( this->*( it->second ) )( j, sample ); @@ -196,17 +196,17 @@ void dds_device::impl::handle_notification( rsutils::json const & j, try { // Check if this is a reply - maybe someone's waiting on it... - if( auto sample = j.find( sample_key ) ) + if( auto sample = j.nested( sample_key ) ) { // [".", ] if( sample.size() == 2 && sample.is_array() ) { // We have to be the ones who sent the control! - auto const reply_guid = guid_from_string( rsutils::json::get< std::string >( sample, 0 ) ); + auto const reply_guid = guid_from_string( sample[0].get< std::string >() ); auto const control_guid = _control_writer->get()->guid(); if( reply_guid == control_guid ) { - auto const sequence_number = rsutils::json::get< uint64_t >( sample, 1 ); + auto const sequence_number = sample[1].get< uint64_t >(); std::unique_lock< std::mutex > lock( _replies_mutex ); auto replyit = _replies.find( sequence_number ); if( replyit != _replies.end() ) @@ -246,13 +246,13 @@ void dds_device::impl::on_option_value( rsutils::json const & j, eprosima::fastd dds_device::check_reply( j ); // We need the original control request as part of the reply, otherwise we can't know what option this is for - auto control = j.find( control_key ); + auto control = j.nested( control_key ); if( ! control.is_object() ) throw std::runtime_error( "missing control object" ); // Find the relevant (stream) options to update dds_options const * options = &_options; - std::string const & stream_name = control.find( stream_name_key ).string_ref_or_empty(); // default = empty = device option + std::string const & stream_name = control.nested( stream_name_key ).string_ref_or_empty(); // default = empty = device option if( ! stream_name.empty() ) { auto stream_it = _streams.find( stream_name ); @@ -276,13 +276,13 @@ void dds_device::impl::on_option_value( rsutils::json const & j, eprosima::fastd LOG_DEBUG( "[" << debug_name() << "] option '" << option_name << "': not found" ); }; - auto value_j = j.find( value_key ); + auto value_j = j.nested( value_key ); if( ! value_j.exists() ) { // Use case: // Bulk query without ANY option names supplied, { "option-name": [] } // There is no 'value' key; instead, the server returns option-value pairs in 'option-values': - auto option_values = j.find( option_values_key ); + auto option_values = j.nested( option_values_key ); if( ! option_values.is_object() ) throw std::runtime_error( "missing value or option-values" ); for( auto it = option_values.begin(); it != option_values.end(); ++it ) @@ -290,7 +290,7 @@ void dds_device::impl::on_option_value( rsutils::json const & j, eprosima::fastd return; } - auto option_name_j = control.find( option_name_key ); + auto option_name_j = control.nested( option_name_key ); if( ! option_name_j.exists() ) throw std::runtime_error( "missing option-name" ); @@ -308,8 +308,8 @@ void dds_device::impl::on_option_value( rsutils::json const & j, eprosima::fastd auto size = value_j.size(); for( auto x = 0; x < size; ++x ) { - auto const & option_name = rsutils::json::string_ref( option_name_j.at( x ) ); - auto const new_value = rsutils::json::value< float >( value_j.at( x ) ); + auto const & option_name = option_name_j[x].string_ref(); + auto const new_value = value_j[x].get< float >(); update_option( option_name, new_value ); } return; @@ -322,7 +322,7 @@ void dds_device::impl::on_option_value( rsutils::json const & j, eprosima::fastd if( ! option_name_j.is_string() ) throw std::runtime_error( "option-name is not a string" ); auto & option_name = option_name_j.string_ref(); - update_option( option_name, rsutils::json::value< float >( value_j ) ); + update_option( option_name, value_j.get< float >() ); } @@ -336,7 +336,7 @@ void dds_device::impl::on_log( rsutils::json const & j, eprosima::fastdds::dds:: { // This is the notification for "log" (see docs/notifications.md#Logging) // - `entries` is an array containing 1 or more log entries - auto entries = j.find( entries_key ); + auto entries = j.nested( entries_key ); if( ! entries ) throw std::runtime_error( "log entries not found" ); if( ! entries.is_array() ) @@ -355,12 +355,12 @@ void dds_device::impl::on_log( rsutils::json const & j, eprosima::fastdds::dds:: throw std::runtime_error( "not an array" ); if( entry.size() < 3 || entry.size() > 4 ) throw std::runtime_error( "bad array length" ); - auto timestamp = rsutils::json::get< dds_nsec >( entry, 0 ); - auto const & stype = rsutils::json::string_ref( entry[1] ); + auto timestamp = entry[0].get< dds_nsec >(); + auto const & stype = entry[1].string_ref(); if( stype.length() != 1 || ! strchr( "EWID", stype[0] ) ) throw std::runtime_error( "type not one of 'EWID'" ); char const type = stype[0]; - auto const & text = rsutils::json::string_ref( entry[2] ); + auto const & text = entry[2].string_ref(); auto const & data = entry.size() > 3 ? entry[3] : rsutils::null_json; if( ! _on_device_log.raise( timestamp, type, text, data ) ) @@ -387,7 +387,7 @@ void dds_device::impl::open( const dds_stream_profiles & profiles ) auto stream = profile->stream(); if( ! stream ) DDS_THROW( runtime_error, "profile " << profile->to_string() << " is not part of any stream" ); - if( stream_profiles.find( stream->name() ) ) + if( stream_profiles.nested( stream->name() ) ) DDS_THROW( runtime_error, "more than one profile found for stream '" << stream->name() << "'" ); stream_profiles[stream->name()] = profile->to_json(); @@ -437,7 +437,7 @@ float dds_device::impl::query_option_value( const std::shared_ptr< dds_option > rsutils::json reply; write_control_message( j, &reply ); - return rsutils::json::get< float >( reply, value_key ); + return reply.at( value_key ).get< float >(); } @@ -445,6 +445,7 @@ void dds_device::impl::write_control_message( topics::flexible_msg && msg, rsuti { assert( _control_writer != nullptr ); auto this_sequence_number = std::move( msg ).write_to( *_control_writer ); + LOG_DEBUG( "[" << debug_name() << "] " << this_sequence_number << " sent" ); if( reply ) { std::unique_lock< std::mutex > lock( _replies_mutex ); @@ -485,7 +486,7 @@ void dds_device::impl::create_notifications_reader() //On discovery writer sends a burst of messages, if history is too small we might loose some of them //(even if reliable). Setting depth to cover known use-cases plus some spare rqos.history().depth = 24; - rqos.override_from_json( rsutils::json::nested( _device_settings, "notification" ) ); + rqos.override_from_json( _device_settings.nested( "notification" ) ); _notifications_reader->on_data_available( [&]() @@ -552,7 +553,7 @@ void dds_device::impl::create_control_writer() _control_writer = std::make_shared< dds_topic_writer >( topic ); dds_topic_writer::qos wqos( eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS ); wqos.history().depth = 10; // default is 1 - wqos.override_from_json( rsutils::json::nested( _device_settings, "control" ) ); + wqos.override_from_json( _device_settings.nested( "control" ) ); _control_writer->run( wqos ); } @@ -566,17 +567,17 @@ void dds_device::impl::on_device_header( rsutils::json const & j, eprosima::fast // with a server. eprosima::fastrtps::rtps::iHandle2GUID( _server_guid, sample.publication_handle ); - _n_streams_expected = rsutils::json::get< size_t >( j, "n-streams" ); + _n_streams_expected = j.at( "n-streams" ).get< size_t >(); LOG_DEBUG( "[" << debug_name() << "] ... " << id_device_header << ": " << _n_streams_expected << " streams expected" ); - if( rsutils::json::has( j, "extrinsics" ) ) + if( auto extrinsics_j = j.nested( "extrinsics" ) ) { - for( auto & ex : j["extrinsics"] ) + for( auto & ex : extrinsics_j ) { - std::string from_name = rsutils::json::get< std::string >( ex, 0 ); - std::string to_name = rsutils::json::get< std::string >( ex, 1 ); + std::string const & from_name = ex[0].string_ref(); + std::string const & to_name = ex[1].string_ref(); LOG_DEBUG( "[" << debug_name() << "] ... got extrinsics from " << from_name << " to " << to_name ); - extrinsics extr = extrinsics::from_json( ex.at( 2 ) ); + extrinsics extr = extrinsics::from_json( ex[2] ); _extrinsics_map[std::make_pair( from_name, to_name )] = std::make_shared< extrinsics >( extr ); } } @@ -590,7 +591,7 @@ void dds_device::impl::on_device_options( rsutils::json const & j, eprosima::fas if( _state != state_t::WAIT_FOR_DEVICE_OPTIONS ) return; - if( auto options_j = j.find( "options" ) ) + if( auto options_j = j.nested( "options" ) ) { LOG_DEBUG( "[" << debug_name() << "] ... " << id_device_options << ": " << options_j.size() << " options received" ); @@ -615,15 +616,15 @@ void dds_device::impl::on_stream_header( rsutils::json const & j, eprosima::fast if( _streams.size() >= _n_streams_expected ) DDS_THROW( runtime_error, "more streams than expected (" << _n_streams_expected << ") received" ); - auto stream_type = rsutils::json::get< std::string >( j, "type" ); - auto stream_name = rsutils::json::get< std::string >( j, "name" ); + auto & stream_type = j.at( "type" ).string_ref(); + auto & stream_name = j.at( "name" ).string_ref(); auto & stream = _streams[stream_name]; if( stream ) DDS_THROW( runtime_error, "stream '" << stream_name << "' already exists" ); - auto sensor_name = rsutils::json::get< std::string >( j, "sensor-name" ); - size_t default_profile_index = rsutils::json::get< size_t >( j, "default-profile-index" ); + auto & sensor_name = j.at( "sensor-name" ).string_ref(); + size_t default_profile_index = j.at( "default-profile-index" ).get< size_t >(); dds_stream_profiles profiles; #define TYPE2STREAM( S, P ) \ @@ -644,7 +645,7 @@ void dds_device::impl::on_stream_header( rsutils::json const & j, eprosima::fast #undef TYPE2STREAM - if( rsutils::json::get< bool >( j, "metadata-enabled" ) ) + if( j.at( "metadata-enabled" ).get< bool >() ) { create_metadata_reader(); stream->enable_metadata(); // Call before init_profiles @@ -674,14 +675,13 @@ void dds_device::impl::on_stream_options( rsutils::json const & j, eprosima::fas if( _state != state_t::WAIT_FOR_STREAM_OPTIONS ) return; - auto stream_it = _streams.find( rsutils::json::get< std::string >( j, "stream-name" ) ); + auto & stream_name = j.at( "stream-name" ).string_ref(); + auto stream_it = _streams.find( stream_name ); if( stream_it == _streams.end() ) DDS_THROW( runtime_error, - std::string( "Received stream options for stream '" ) - + rsutils::json::get< std::string >( j, "stream-name" ) - + "' whose header was not received yet" ); + "Received stream options for stream '" << stream_name << "' whose header was not received yet" ); - if( auto options_j = j.find( "options" ) ) + if( auto options_j = j.nested( "options" ) ) { dds_options options; for( auto & option : options_j ) @@ -692,7 +692,7 @@ void dds_device::impl::on_stream_options( rsutils::json const & j, eprosima::fas stream_it->second->init_options( options ); } - if( auto j_int = j.find( "intrinsics" ) ) + if( auto j_int = j.nested( "intrinsics" ) ) { if( auto video_stream = std::dynamic_pointer_cast< dds_video_stream >( stream_it->second ) ) { @@ -708,7 +708,7 @@ void dds_device::impl::on_stream_options( rsutils::json const & j, eprosima::fas } } - if( auto filters_j = j.find( "recommended-filters" ) ) + if( auto filters_j = j.nested( "recommended-filters" ) ) { std::vector< std::string > filter_names; for( auto & filter : filters_j ) diff --git a/third-party/realdds/src/dds-device-server.cpp b/third-party/realdds/src/dds-device-server.cpp index 45a4d5571ce..a4d6fa2dc72 100644 --- a/third-party/realdds/src/dds-device-server.cpp +++ b/third-party/realdds/src/dds-device-server.cpp @@ -139,8 +139,8 @@ static void on_discovery_stream_header( std::shared_ptr< dds_stream_server > con else if( auto motion_stream = std::dynamic_pointer_cast< dds_motion_stream_server >( stream ) ) { intrinsics = rsutils::json::object( { - { "accel", motion_stream->get_accel_intrinsics().to_json().moved() }, - { "gyro", motion_stream->get_gyro_intrinsics().to_json().moved() } + { "accel", motion_stream->get_accel_intrinsics().to_json() }, + { "gyro", motion_stream->get_gyro_intrinsics().to_json() } } ); } @@ -151,7 +151,7 @@ static void on_discovery_stream_header( std::shared_ptr< dds_stream_server > con { id_key, "stream-options" }, { "stream-name", stream->name() }, { "options", std::move( stream_options ) }, - { "intrinsics", intrinsics.moved() }, + { "intrinsics", intrinsics }, { "recommended-filters", std::move( stream_filters ) }, } ) ); json_string = slice( stream_options_message.custom_data< char const >(), stream_options_message._data.size() ); @@ -206,8 +206,7 @@ void dds_device_server::init( std::vector< std::shared_ptr< dds_stream_server > _metadata_writer = std::make_shared< dds_topic_writer >( topic, _publisher ); dds_topic_writer::qos wqos( eprosima::fastdds::dds::BEST_EFFORT_RELIABILITY_QOS ); wqos.history().depth = 10; // default is 1 - wqos.override_from_json( - rsutils::json::nested( _subscriber->get_participant()->settings(), "device", "metadata" ) ); + wqos.override_from_json( _subscriber->get_participant()->settings().nested( "device", "metadata" ) ); _metadata_writer->run( wqos ); } } @@ -221,7 +220,7 @@ void dds_device_server::init( std::vector< std::shared_ptr< dds_stream_server > _control_reader->on_data_available( [&]() { on_control_message_received(); } ); dds_topic_reader::qos rqos( RELIABLE_RELIABILITY_QOS ); - rqos.override_from_json( rsutils::json::nested( _subscriber->get_participant()->settings(), "device", "control" ) ); + rqos.override_from_json( _subscriber->get_participant()->settings().nested( "device", "control" ) ); _control_reader->run( rqos ); } catch( std::exception const & ) @@ -281,14 +280,16 @@ void dds_device_server::on_control_message_received() _control_dispatcher.invoke( [j = data.json_data(), sample = info, this]( dispatcher::cancellable_timer ) { - json reply; - reply[sample_key] = json::array( { + auto sample_j = json::array( { rsutils::string::from( realdds::print_raw_guid( sample.sample_identity.writer_guid() ) ), sample.sample_identity.sequence_number().to64long(), } ); + LOG_DEBUG( "<----- control " << sample_j << ": " << j ); + json reply; + reply[sample_key] = std::move( sample_j ); try { - std::string id = rsutils::json::get< std::string >( j, id_key ); + std::string const & id = j.at( id_key ).string_ref(); reply[id_key] = id; reply[control_key] = j; handle_control_message( id, j, reply ); @@ -298,9 +299,9 @@ void dds_device_server::on_control_message_received() reply[status_key] = "error"; reply[explanation_key] = e.what(); } + LOG_DEBUG( "-----> reply " << reply ); try { - LOG_DEBUG( "-----> reply " << reply ); publish_notification( reply ); } catch( ... ) @@ -316,8 +317,6 @@ void dds_device_server::handle_control_message( std::string const & id, rsutils::json const & j, rsutils::json & reply ) { - LOG_DEBUG( "<----- control " << j ); - if( id.compare( id_set_option ) == 0 ) { handle_set_option( j, reply ); @@ -328,21 +327,21 @@ void dds_device_server::handle_control_message( std::string const & id, } else if( ! _control_callback || ! _control_callback( id, j, reply ) ) { - DDS_THROW( runtime_error, "invalid control '" + id + "'" ); + DDS_THROW( runtime_error, "invalid control" ); } } void dds_device_server::handle_set_option( const rsutils::json & j, rsutils::json & reply ) { - auto option_name = rsutils::json::get< std::string >( j, option_name_key ); + auto & option_name = j.at( option_name_key ).string_ref(); std::string stream_name; // default is empty, for a device option - rsutils::json::get_ex( j, stream_name_key, &stream_name ); + j.nested( stream_name_key ).get_ex( stream_name ); std::shared_ptr< dds_option > opt = find_option( option_name, stream_name ); if( opt ) { - float value = rsutils::json::get< float >( j, value_key ); + float value = j.at( value_key ).get< float >(); if( _set_option_callback ) _set_option_callback( opt, value ); //Handle setting option outside realdds opt->set_value( value ); //Update option object. Do second to check if _set_option_callback did not throw @@ -354,7 +353,7 @@ void dds_device_server::handle_set_option( const rsutils::json & j, rsutils::jso stream_name = "device"; else stream_name = "'" + stream_name + "'"; - DDS_THROW( runtime_error, stream_name + " option '" + option_name + "' not found" ); + DDS_THROW( runtime_error, stream_name << " option '" << option_name << "' not found" ); } } @@ -362,7 +361,7 @@ void dds_device_server::handle_set_option( const rsutils::json & j, rsutils::jso void dds_device_server::handle_query_option( const rsutils::json & j, rsutils::json & reply ) { std::string stream_name; // default is empty, for a device option - rsutils::json::get_ex( j, stream_name_key, &stream_name ); + j.nested( stream_name_key ).get_ex( stream_name ); auto query_option = [&]( std::shared_ptr< dds_option > const & option ) { @@ -401,7 +400,7 @@ void dds_device_server::handle_query_option( const rsutils::json & j, rsutils::j if( option_name.empty() ) { // Query all options and return in option:value object - rsutils::json_type & option_values = reply[option_values_key] = rsutils::json::object(); + rsutils::json & option_values = reply[option_values_key] = rsutils::json::object(); if( stream_name.empty() ) { for( auto const & option : _options ) @@ -419,7 +418,7 @@ void dds_device_server::handle_query_option( const rsutils::json & j, rsutils::j } else { - rsutils::json_type & value = reply[value_key]; + rsutils::json & value = reply[value_key]; for( auto x = 0; x < option_name.size(); ++x ) value.push_back( query_option_j( option_name.at( x ) ) ); } diff --git a/third-party/realdds/src/dds-device-watcher.cpp b/third-party/realdds/src/dds-device-watcher.cpp index 0573a7f42b7..1fa669d2141 100644 --- a/third-party/realdds/src/dds-device-watcher.cpp +++ b/third-party/realdds/src/dds-device-watcher.cpp @@ -51,7 +51,7 @@ dds_device_watcher::dds_device_watcher( std::shared_ptr< dds_participant > const } auto j = msg.json_data(); - if( j.find( "stopping" ) ) + if( j.nested( "stopping" ) ) { // This device is stopping for whatever reason (e.g., HW reset); remove it LOG_DEBUG( "DDS device (from " << _participant->print( guid ) << ") is stopping" ); diff --git a/third-party/realdds/src/dds-device.cpp b/third-party/realdds/src/dds-device.cpp index 8adbee44f89..98ecc21cfa6 100644 --- a/third-party/realdds/src/dds-device.cpp +++ b/third-party/realdds/src/dds-device.cpp @@ -143,7 +143,7 @@ static std::string const id_key( "id", 2 ); bool dds_device::check_reply( rsutils::json const & reply, std::string * p_explanation ) { - auto status_j = reply.find( status_key ); + auto status_j = reply.nested( status_key ); if( ! status_j ) return true; std::ostringstream os; @@ -154,13 +154,13 @@ bool dds_device::check_reply( rsutils::json const & reply, std::string * p_expla else { os << "["; - if( auto id = reply.find( id_key ) ) + if( auto id = reply.nested( id_key ) ) { if( id.is_string() ) os << "\"" << id.string_ref() << "\" "; } os << status_j.string_ref() << "]"; - if( auto explanation_j = reply.find( explanation_key ) ) + if( auto explanation_j = reply.nested( explanation_key ) ) { os << ' '; if( explanation_j.string_ref_or_empty().empty() ) diff --git a/third-party/realdds/src/dds-notification-server.cpp b/third-party/realdds/src/dds-notification-server.cpp index 583b678a084..baa4084e420 100644 --- a/third-party/realdds/src/dds-notification-server.cpp +++ b/third-party/realdds/src/dds-notification-server.cpp @@ -91,7 +91,7 @@ dds_notification_server::dds_notification_server( std::shared_ptr< dds_publisher // If history is too small writer will not be able to re-transmit needed samples. // Setting history to cover known use-cases plus some spare wqos.history().depth = 24; - wqos.override_from_json( rsutils::json::nested( publisher->get_participant()->settings(), "device", "notification" ) ); + wqos.override_from_json( publisher->get_participant()->settings().nested( "device", "notification" ) ); _writer->run( wqos ); } diff --git a/third-party/realdds/src/dds-option.cpp b/third-party/realdds/src/dds-option.cpp index ed0a4c0670b..0a8ca29efbf 100644 --- a/third-party/realdds/src/dds-option.cpp +++ b/third-party/realdds/src/dds-option.cpp @@ -23,13 +23,13 @@ dds_option::dds_option( rsutils::json const & j ) { int index = 0; - _name = rsutils::json::get< std::string >( j, index++ ); - _value = rsutils::json::get< float >( j, index++ ); - _range.min = rsutils::json::get< float >( j, index++ ); - _range.max = rsutils::json::get< float >( j, index++ ); - _range.step = rsutils::json::get< float >( j, index++ ); - _range.default_value = rsutils::json::get< float >( j, index++ ); - _description = rsutils::json::get< std::string >( j, index++ ); + _name = j[index++].string_ref(); + _value = j[index++].get< float >(); + _range.min = j[index++].get< float >(); + _range.max = j[index++].get< float >(); + _range.step = j[index++].get< float >(); + _range.default_value = j[index++].get< float >(); + _description = j[index++].string_ref(); if( index != j.size() ) DDS_THROW( runtime_error, "expected end of json at index " << index ); @@ -39,7 +39,7 @@ dds_option::dds_option( rsutils::json const & j ) void dds_option::init_stream( std::shared_ptr< dds_stream_base > const & stream ) { if( _stream.lock() ) - DDS_THROW( runtime_error, "option '" + get_name() + "' already has a stream" ); + DDS_THROW( runtime_error, "option '" << get_name() << "' already has a stream" ); if( ! stream ) DDS_THROW( runtime_error, "null stream" ); _stream = stream; diff --git a/third-party/realdds/src/dds-participant.cpp b/third-party/realdds/src/dds-participant.cpp index 33f8c05d875..5412f17fc3b 100644 --- a/third-party/realdds/src/dds-participant.cpp +++ b/third-party/realdds/src/dds-participant.cpp @@ -198,8 +198,7 @@ void dds_participant::init( dds_domain_id domain_id, std::string const & partici if( domain_id == -1 ) { // Get it from settings and default to 0 - if( ! rsutils::json::get_ex( settings, "domain", &domain_id ) ) - domain_id = 0; + domain_id = settings.nested( "domain" ).default_value( 0 ); } _domain_listener = std::make_shared< listener_impl >( *this ); diff --git a/third-party/realdds/src/dds-serialization.cpp b/third-party/realdds/src/dds-serialization.cpp index 29582e782aa..51a427603d0 100644 --- a/third-party/realdds/src/dds-serialization.cpp +++ b/third-party/realdds/src/dds-serialization.cpp @@ -118,7 +118,7 @@ namespace fastrtps { // Allow j["key"] = qos.lease_duration; -void to_json( rsutils::json_type & j, Duration_t const & duration ) +void to_json( rsutils::json & j, Duration_t const & duration ) { if( duration == c_TimeInfinite ) j = "infinite"; @@ -130,17 +130,17 @@ void to_json( rsutils::json_type & j, Duration_t const & duration ) // Allow j.get< eprosima::fastrtps::Duration_t >(); -void from_json( rsutils::json_type const & j, Duration_t & duration ) +void from_json( rsutils::json const & j, Duration_t & duration ) { if( j.is_string() ) { - auto & s = rsutils::json::string_ref( j ); + auto & s = j.string_ref(); if( rsutils::string::nocase_equal( s, "infinite" ) ) duration = c_TimeInfinite; else if( rsutils::string::nocase_equal( s, "invalid" ) ) duration = c_TimeInvalid; else - throw rsutils::json_type::type_error::create( 317, "unknown duration value '" + s + "'", &j ); + throw rsutils::json::type_error::create( 317, "unknown duration value '" + s + "'", &j ); } else duration = realdds::dds_time( j.get< double >() ); @@ -249,12 +249,11 @@ void override_reliability_qos_from_json( eprosima::fastdds::dds::ReliabilityQosP if( j.is_null() ) return; if( j.is_string() ) - qos.kind = reliability_kind_from_string( rsutils::json::string_ref( j ) ); + qos.kind = reliability_kind_from_string( j.string_ref() ); else if( j.is_object() ) { - std::string kind_str; - if( rsutils::json::get_ex( j, "kind", &kind_str ) ) - qos.kind = reliability_kind_from_string( kind_str ); + if( auto kind_j = j.nested( "kind", &rsutils::json::is_string ) ) + qos.kind = reliability_kind_from_string( kind_j.string_ref() ); } } @@ -267,9 +266,8 @@ void override_durability_qos_from_json( eprosima::fastdds::dds::DurabilityQosPol qos.kind = durability_kind_from_string( j.get_ref< const rsutils::json::string_t & >() ); else if( j.is_object() ) { - std::string kind_str; - if( rsutils::json::get_ex( j, "kind", &kind_str ) ) - qos.kind = durability_kind_from_string( kind_str ); + if( auto kind_j = j.nested( "kind", &rsutils::json::is_string ) ) + qos.kind = durability_kind_from_string( kind_j.string_ref() ); } } @@ -279,13 +277,12 @@ void override_history_qos_from_json( eprosima::fastdds::dds::HistoryQosPolicy & if( j.is_null() ) return; if( j.is_number_unsigned() ) - qos.depth = rsutils::json::value< int32_t >( j ); + qos.depth = j.get< int32_t >(); else if( j.is_object() ) { - std::string kind_str; - if( rsutils::json::get_ex( j, "kind", &kind_str ) ) - qos.kind = history_kind_from_string( kind_str ); - rsutils::json::get_ex( j, "depth", &qos.depth ); + if( auto kind_j = j.nested( "kind", &rsutils::json::is_string ) ) + qos.kind = history_kind_from_string( kind_j.string_ref() ); + j.nested( "depth" ).get_ex( qos.depth ); } } @@ -294,7 +291,7 @@ void override_liveliness_qos_from_json( eprosima::fastdds::dds::LivelinessQosPol { if( j.is_object() ) { - if( auto kind = rsutils::json::nested( j, "kind" ) ) + if( auto kind = j.nested( "kind" ) ) { if( kind.is_string() ) qos.kind = liveliness_kind_from_string( kind.string_ref() ); @@ -302,20 +299,20 @@ void override_liveliness_qos_from_json( eprosima::fastdds::dds::LivelinessQosPol DDS_THROW( runtime_error, "liveliness kind not a string: " << kind ); } - if( auto lease = rsutils::json::nested( j, "lease-duration" ) ) + if( auto lease = j.nested( "lease-duration" ) ) { if( lease.is_null() ) qos.lease_duration = eprosima::fastdds::dds::LivelinessQosPolicy().lease_duration; else - lease.value_to( qos.lease_duration ); + lease.get_to( qos.lease_duration ); } - if( auto announce = rsutils::json::nested( j, "announcement-period" ) ) + if( auto announce = j.nested( "announcement-period" ) ) { if( announce.is_null() ) qos.announcement_period = eprosima::fastdds::dds::LivelinessQosPolicy().announcement_period; else - announce.value_to( qos.announcement_period ); + announce.get_to( qos.announcement_period ); } } } @@ -325,7 +322,7 @@ void override_data_sharing_qos_from_json( eprosima::fastdds::dds::DataSharingQos { if( j.is_boolean() ) { - if( rsutils::json::value< bool >( j ) ) + if( j.get< bool >() ) qos.automatic(); else qos.off(); @@ -343,16 +340,15 @@ void override_endpoint_qos_from_json( eprosima::fastdds::dds::RTPSEndpointQos & return; if( j.is_object() ) { - std::string policy_str; - if( rsutils::json::get_ex( j, "history-memory-policy", &policy_str ) ) - qos.history_memory_policy = history_memory_policy_from_string( policy_str ); + if( auto policy_j = j.nested( "history-memory-policy", &rsutils::json::is_string ) ) + qos.history_memory_policy = history_memory_policy_from_string( policy_j.string_ref() ); } } static bool parse_ip_list( rsutils::json const & j, std::string const & key, std::vector< std::string > * output ) { - if( auto whitelist_j = rsutils::json::nested( j, key ) ) + if( auto whitelist_j = j.nested( key ) ) { if( ! whitelist_j.is_array() ) return false; @@ -361,17 +357,17 @@ static bool parse_ip_list( rsutils::json const & j, std::string const & key, std if( ! ip.is_string() ) return false; if( output ) - output->push_back( rsutils::json::string_ref( ip ) ); + output->push_back( ip.string_ref() ); } } return true; } -static void override_udp_settings( eprosima::fastdds::rtps::UDPTransportDescriptor & udp, rsutils::json_type const & j ) +static void override_udp_settings( eprosima::fastdds::rtps::UDPTransportDescriptor & udp, rsutils::json const & j ) { - rsutils::json::get_ex( j, "send-buffer-size", &udp.sendBufferSize ); - rsutils::json::get_ex( j, "receive-buffer-size", &udp.receiveBufferSize ); + j.nested( "send-buffer-size" ).get_ex( udp.sendBufferSize ); + j.nested( "receive-buffer-size" ).get_ex( udp.receiveBufferSize ); if( ! parse_ip_list( j, "whitelist", &udp.interfaceWhiteList ) ) LOG_WARNING( "invalid UDP whitelist in settings" ); } @@ -381,11 +377,11 @@ void override_participant_qos_from_json( eprosima::fastdds::dds::DomainParticipa { if( ! j.is_object() ) return; - rsutils::json::get_ex( j, "participant-id", &qos.wire_protocol().participant_id ); - rsutils::json::get_ex( j, "lease-duration", &qos.wire_protocol().builtin.discovery_config.leaseDuration ); + j.nested( "participant-id" ).get_ex( qos.wire_protocol().participant_id ); + j.nested( "lease-duration" ).get_ex( qos.wire_protocol().builtin.discovery_config.leaseDuration ); - rsutils::json::get_ex( j, "use-builtin-transports", &qos.transport().use_builtin_transports ); - if( auto udp_j = rsutils::json::nested( j, "udp" ) ) + j.nested( "use-builtin-transports" ).get_ex( qos.transport().use_builtin_transports ); + if( auto udp_j = j.nested( "udp" ) ) { for( auto t : qos.transport().user_transports ) if( auto udp_t = std::dynamic_pointer_cast< eprosima::fastdds::rtps::UDPTransportDescriptor >( t ) ) diff --git a/third-party/realdds/src/dds-stream-profile.cpp b/third-party/realdds/src/dds-stream-profile.cpp index 8ad0720826a..235a57d5e5a 100644 --- a/third-party/realdds/src/dds-stream-profile.cpp +++ b/third-party/realdds/src/dds-stream-profile.cpp @@ -136,7 +136,7 @@ dds_video_encoding dds_video_encoding::from_rs2( int rs2_format ) dds_stream_profile::dds_stream_profile( rsutils::json const & j, int & it ) - : _frequency( rsutils::json::get< int16_t >( j, it++ ) ) + : _frequency( j[it++].get< int16_t >() ) { // NOTE: the order of construction is the order of declaration -- therefore the to_json() function // should use the same ordering! @@ -196,10 +196,10 @@ rsutils::json dds_stream_profile::to_json() const dds_video_stream_profile::dds_video_stream_profile( rsutils::json const & j, int & index ) : super( j, index ) - , _encoding( rsutils::json::get< std::string >( j, index++ ) ) + , _encoding( j[index++].get< std::string >() ) { - _width = rsutils::json::get< int16_t >( j, index++ ); - _height = rsutils::json::get< int16_t >( j, index++ ); + _width = j[index++].get< int16_t >(); + _height = j[index++].get< int16_t >(); } diff --git a/third-party/realdds/src/dds-topic-reader.cpp b/third-party/realdds/src/dds-topic-reader.cpp index d412c928e69..fe9c578fd44 100644 --- a/third-party/realdds/src/dds-topic-reader.cpp +++ b/third-party/realdds/src/dds-topic-reader.cpp @@ -94,12 +94,12 @@ void dds_topic_reader::qos::override_from_json( rsutils::json const & qos_settin if( qos_settings.is_null() ) return; - override_reliability_qos_from_json( reliability(), rsutils::json::nested( qos_settings, "reliability" ) ); - override_durability_qos_from_json( durability(), rsutils::json::nested( qos_settings, "durability" ) ); - override_history_qos_from_json( history(), rsutils::json::nested( qos_settings, "history" ) ); - override_liveliness_qos_from_json( liveliness(), rsutils::json::nested( qos_settings, "liveliness" ) ); - override_data_sharing_qos_from_json( data_sharing(), rsutils::json::nested( qos_settings, "data-sharing" ) ); - override_endpoint_qos_from_json( endpoint(), rsutils::json::nested( qos_settings, "endpoint" ) ); + override_reliability_qos_from_json( reliability(), qos_settings.nested( "reliability" ) ); + override_durability_qos_from_json( durability(), qos_settings.nested( "durability" ) ); + override_history_qos_from_json( history(), qos_settings.nested( "history" ) ); + override_liveliness_qos_from_json( liveliness(), qos_settings.nested( "liveliness" ) ); + override_data_sharing_qos_from_json( data_sharing(), qos_settings.nested( "data-sharing" ) ); + override_endpoint_qos_from_json( endpoint(), qos_settings.nested( "endpoint" ) ); } diff --git a/third-party/realdds/src/dds-topic-writer.cpp b/third-party/realdds/src/dds-topic-writer.cpp index aee4332295b..0a38d0483aa 100644 --- a/third-party/realdds/src/dds-topic-writer.cpp +++ b/third-party/realdds/src/dds-topic-writer.cpp @@ -94,12 +94,12 @@ void dds_topic_writer::qos::override_from_json( rsutils::json const & qos_settin if( qos_settings.is_null() ) return; - override_reliability_qos_from_json( reliability(), qos_settings.find( "reliability" ) ); - override_durability_qos_from_json( durability(), rsutils::json::nested( qos_settings, "durability" ) ); - override_history_qos_from_json( history(), rsutils::json::nested( qos_settings, "history" ) ); - override_liveliness_qos_from_json( liveliness(), rsutils::json::nested( qos_settings, "liveliness" ) ); - override_data_sharing_qos_from_json( data_sharing(), rsutils::json::nested( qos_settings, "data-sharing" ) ); - override_endpoint_qos_from_json( endpoint(), rsutils::json::nested( qos_settings, "endpoint" ) ); + override_reliability_qos_from_json( reliability(), qos_settings.nested( "reliability" ) ); + override_durability_qos_from_json( durability(), qos_settings.nested( "durability" ) ); + override_history_qos_from_json( history(), qos_settings.nested( "history" ) ); + override_liveliness_qos_from_json( liveliness(), qos_settings.nested( "liveliness" ) ); + override_data_sharing_qos_from_json( data_sharing(), qos_settings.nested( "data-sharing" ) ); + override_endpoint_qos_from_json( endpoint(), qos_settings.nested( "endpoint" ) ); } diff --git a/third-party/realdds/src/dds-trinsics.cpp b/third-party/realdds/src/dds-trinsics.cpp index 988bec967ce..e9ff1d16528 100644 --- a/third-party/realdds/src/dds-trinsics.cpp +++ b/third-party/realdds/src/dds-trinsics.cpp @@ -23,18 +23,18 @@ rsutils::json video_intrinsics::to_json() const video_intrinsics ret; int index = 0; - ret.width = rsutils::json::get< int >( j, index++ ); - ret.height = rsutils::json::get< int >( j, index++ ); - ret.principal_point_x = rsutils::json::get< float >( j, index++ ); - ret.principal_point_y = rsutils::json::get< float >( j, index++ ); - ret.focal_lenght_x = rsutils::json::get< float >( j, index++ ); - ret.focal_lenght_y = rsutils::json::get< float >( j, index++ ); - ret.distortion_model = rsutils::json::get< int >( j, index++ ); - ret.distortion_coeffs[0] = rsutils::json::get< float >( j, index++ ); - ret.distortion_coeffs[1] = rsutils::json::get< float >( j, index++ ); - ret.distortion_coeffs[2] = rsutils::json::get< float >( j, index++ ); - ret.distortion_coeffs[3] = rsutils::json::get< float >( j, index++ ); - ret.distortion_coeffs[4] = rsutils::json::get< float >( j, index++ ); + ret.width = j[index++].get< int >(); + ret.height = j[index++].get< int >(); + ret.principal_point_x = j[index++].get< float >(); + ret.principal_point_y = j[index++].get< float >(); + ret.focal_lenght_x = j[index++].get< float >(); + ret.focal_lenght_y = j[index++].get< float >(); + ret.distortion_model = j[index++].get< int >(); + ret.distortion_coeffs[0] = j[index++].get< float >(); + ret.distortion_coeffs[1] = j[index++].get< float >(); + ret.distortion_coeffs[2] = j[index++].get< float >(); + ret.distortion_coeffs[3] = j[index++].get< float >(); + ret.distortion_coeffs[4] = j[index++].get< float >(); if( index != j.size() ) DDS_THROW( runtime_error, "expected end of json at index " + std::to_string( index ) ); @@ -58,24 +58,24 @@ rsutils::json motion_intrinsics::to_json() const motion_intrinsics ret; int index = 0; - ret.data[0][0] = rsutils::json::get< float >( j, index++ ); - ret.data[0][1] = rsutils::json::get< float >( j, index++ ); - ret.data[0][2] = rsutils::json::get< float >( j, index++ ); - ret.data[0][3] = rsutils::json::get< float >( j, index++ ); - ret.data[1][0] = rsutils::json::get< float >( j, index++ ); - ret.data[1][1] = rsutils::json::get< float >( j, index++ ); - ret.data[1][2] = rsutils::json::get< float >( j, index++ ); - ret.data[1][3] = rsutils::json::get< float >( j, index++ ); - ret.data[2][0] = rsutils::json::get< float >( j, index++ ); - ret.data[2][1] = rsutils::json::get< float >( j, index++ ); - ret.data[2][2] = rsutils::json::get< float >( j, index++ ); - ret.data[2][3] = rsutils::json::get< float >( j, index++ ); - ret.noise_variances[0] = rsutils::json::get< float >( j, index++ ); - ret.noise_variances[1] = rsutils::json::get< float >( j, index++ ); - ret.noise_variances[2] = rsutils::json::get< float >( j, index++ ); - ret.bias_variances[0] = rsutils::json::get< float >( j, index++ ); - ret.bias_variances[1] = rsutils::json::get< float >( j, index++ ); - ret.bias_variances[2] = rsutils::json::get< float >( j, index++ ); + ret.data[0][0] = j[index++].get< float >(); + ret.data[0][1] = j[index++].get< float >(); + ret.data[0][2] = j[index++].get< float >(); + ret.data[0][3] = j[index++].get< float >(); + ret.data[1][0] = j[index++].get< float >(); + ret.data[1][1] = j[index++].get< float >(); + ret.data[1][2] = j[index++].get< float >(); + ret.data[1][3] = j[index++].get< float >(); + ret.data[2][0] = j[index++].get< float >(); + ret.data[2][1] = j[index++].get< float >(); + ret.data[2][2] = j[index++].get< float >(); + ret.data[2][3] = j[index++].get< float >(); + ret.noise_variances[0] = j[index++].get< float >(); + ret.noise_variances[1] = j[index++].get< float >(); + ret.noise_variances[2] = j[index++].get< float >(); + ret.bias_variances[0] = j[index++].get< float >(); + ret.bias_variances[1] = j[index++].get< float >(); + ret.bias_variances[2] = j[index++].get< float >(); if( index != j.size() ) DDS_THROW( runtime_error, "expected end of json at index " + std::to_string( index ) ); @@ -98,18 +98,18 @@ rsutils::json extrinsics::to_json() const extrinsics ret; int index = 0; - ret.rotation[0] = rsutils::json::get< float >( j, index++ ); - ret.rotation[1] = rsutils::json::get< float >( j, index++ ); - ret.rotation[2] = rsutils::json::get< float >( j, index++ ); - ret.rotation[3] = rsutils::json::get< float >( j, index++ ); - ret.rotation[4] = rsutils::json::get< float >( j, index++ ); - ret.rotation[5] = rsutils::json::get< float >( j, index++ ); - ret.rotation[6] = rsutils::json::get< float >( j, index++ ); - ret.rotation[7] = rsutils::json::get< float >( j, index++ ); - ret.rotation[8] = rsutils::json::get< float >( j, index++ ); - ret.translation[0] = rsutils::json::get< float >( j, index++ ); - ret.translation[1] = rsutils::json::get< float >( j, index++ ); - ret.translation[2] = rsutils::json::get< float >( j, index++ ); + ret.rotation[0] = j[index++].get< float >(); + ret.rotation[1] = j[index++].get< float >(); + ret.rotation[2] = j[index++].get< float >(); + ret.rotation[3] = j[index++].get< float >(); + ret.rotation[4] = j[index++].get< float >(); + ret.rotation[5] = j[index++].get< float >(); + ret.rotation[6] = j[index++].get< float >(); + ret.rotation[7] = j[index++].get< float >(); + ret.rotation[8] = j[index++].get< float >(); + ret.translation[0] = j[index++].get< float >(); + ret.translation[1] = j[index++].get< float >(); + ret.translation[2] = j[index++].get< float >(); if( index != j.size() ) DDS_THROW( runtime_error, "expected end of json at index " + std::to_string( index ) ); diff --git a/third-party/realdds/src/topics/device-info-msg.cpp b/third-party/realdds/src/topics/device-info-msg.cpp index 73f6d63fdfd..141559b52db 100644 --- a/third-party/realdds/src/topics/device-info-msg.cpp +++ b/third-party/realdds/src/topics/device-info-msg.cpp @@ -39,7 +39,7 @@ rsutils::json const & device_info::to_json() const std::string const & device_info::name() const { - return rsutils::json::nested( _json, name_key ).string_ref_or_empty(); + return _json.nested( name_key ).string_ref_or_empty(); } void device_info::set_name( std::string const & v ) @@ -50,7 +50,7 @@ void device_info::set_name( std::string const & v ) std::string const & device_info::topic_root() const { - return rsutils::json::nested( _json, topic_root_key ).string_ref_or_empty(); + return _json.nested( topic_root_key ).string_ref_or_empty(); } void device_info::set_topic_root( std::string const & v ) @@ -61,7 +61,7 @@ void device_info::set_topic_root( std::string const & v ) std::string const & device_info::serial_number() const { - return rsutils::json::nested( _json, serial_number_key ).string_ref_or_empty(); + return _json.nested( serial_number_key ).string_ref_or_empty(); } void device_info::set_serial_number( std::string const & v ) diff --git a/third-party/rsutils/include/rsutils/json-config.h b/third-party/rsutils/include/rsutils/json-config.h new file mode 100644 index 00000000000..31e8d84940a --- /dev/null +++ b/third-party/rsutils/include/rsutils/json-config.h @@ -0,0 +1,29 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2023 Intel Corporation. All Rights Reserved. +#pragma once + +#include + + +namespace rsutils { +namespace json_config { + + +// Load the contents of a file into a JSON object. +json load_from_file( std::string const & filename ); + +// Recursively patches existing 'j' with contents of 'overrides', which must be a JSON object +void override( json_ref overrides, std::string const & what = {} ); + +// Loads configuration settings from 'global' content +json load_app_settings( json const & global, + std::string const & application, + json_key const & subkey, + std::string const & error_context ); + +// Same as above, but automatically takes the application name from the executable-name +json load_settings( json const & global, json_key const & subkey, std::string const & error_context ); + + +} // namespace json_config +} // namespace rsutils diff --git a/third-party/rsutils/include/rsutils/json-fwd.h b/third-party/rsutils/include/rsutils/json-fwd.h index cbf6eeccb03..03ca1fd64a5 100644 --- a/third-party/rsutils/include/rsutils/json-fwd.h +++ b/third-party/rsutils/include/rsutils/json-fwd.h @@ -8,16 +8,71 @@ namespace rsutils { -using json_key = std::string; -using json_type = nlohmann::json; +using json_key = std::string; // default of basic_json -class json_ref; -class json; +class json_ref; // Our 'nested' json const reference wrapper +class json_base; // The 'json' base class -extern json_type const null_json; -extern json_type const empty_json_string; -extern json_type const empty_json_object; + +// We use a custom base class to inject our own functionality into every 'json' object: +// json <- nlohmann::basic_json<...> <- json_base +// +// NOTE: everything here is templated or forward-declared: neither 'json' nor 'json_ref' are defined yet, and 'json' +// can't even be referred to because there's a circular dependency (it derives from json_base). +// +// This class defines new functionality that the basic 'json' doesn't have that we want (like nested()), but the actual +// implementations are always in 'json_ref'! +// +// WARNING: we cannot override already-existing 'json' functions! In fact, it's the other way around: 'json' functions +// will override ours, so, for example, if we defined find() it would not be usable. +// +class json_base +{ + // Since we know how we're derived from, we can get the json, or the reference to it, from this: + inline json_ref _ref() const; + +public: + inline bool exists() const; + + template< class T > inline T default_value( T const & default_value ) const; + + template< class T > inline bool get_ex( T & value ) const; + + inline json_ref default_object() const; + inline json_ref default_string() const; + + inline std::string const & string_ref() const; + inline std::string const & string_ref_or_empty() const; + + template< typename... Rest > inline json_ref nested( Rest... rest ) const; + template< typename... Rest > inline json_ref nested_check( Rest... rest ) const; + + // Recursively patches with contents of 'overrides', which must be a JSON object + void override( json_ref overrides, std::string const & what = {} ); + +}; + + + +using json = nlohmann::basic_json< std::map, // all template arguments are defaults + std::vector, + json_key, + bool, + std::int64_t, + std::uint64_t, + double, + std::allocator, + nlohmann::adl_serializer, + std::vector< std::uint8_t >, + json_base >; // except custom base class! + + +// We can't put these inside json, unfortunately... +extern json const null_json; // default json state +extern json const missing_json; // i.e., not there: exists() will be 'false' +extern json const empty_json_string; +extern json const empty_json_object; } // namespace rsutils diff --git a/third-party/rsutils/include/rsutils/json.h b/third-party/rsutils/include/rsutils/json.h index eac347521fa..d9a83d33a84 100644 --- a/third-party/rsutils/include/rsutils/json.h +++ b/third-party/rsutils/include/rsutils/json.h @@ -9,261 +9,240 @@ namespace rsutils { -class json : public json_type +template< typename... Rest > +json_ref _nested_json( json const & j ); +template< typename... Rest > +json_ref _nested_json( json const & j, bool (json:: * is_fn)() const ); +template< typename... Rest > +json_ref _nested_json( json const & j, json_key const & inner, Rest... rest ); +template< typename... Rest > +json_ref _nested_json( json const & j, json::size_type index, Rest... rest ); + + +#if 0 +inline std::string _nested_json_path( json_key const & a ) { return a; } +inline std::string _nested_json_path( json::size_type x ) { return '[' + std::to_string( x ) + ']'; } + +template< typename... Rest > +std::string _nested_json_path( json_key const & a, json_key const & b, Rest... rest ) { - json_type const & _j() const { return static_cast< json_type const & >( *this ); } + return a + '/' + _nested_json_path( b, std::forward< Rest >( rest )... ); +} +template< typename... Rest > +std::string _nested_json_path( json_key const & a, json::size_type b, Rest... rest ) +{ + return a + _nested_json_path( b, std::forward< Rest >( rest )... ); +} +template< typename... Rest > +std::string _nested_json_path( json::size_type a, json::size_type b, Rest... rest ) +{ + return _nested_json_path( a ) + _nested_json_path( b, std::forward< Rest >( rest )... ); +} +template< typename... Rest > +std::string _nested_json_path( json::size_type a, json_key const & b, Rest... rest ) +{ + return _nested_json_path( a ) + '/' + _nested_json_path( b, std::forward< Rest >( rest )... ); +} +template< typename... Rest > +static std::string _nested_json_path( json::size_type index, Rest... rest ) +{ + return a + '[' + std::to_string( index ) + ']' + _nested_json_path( std::forward< Rest >( rest )... ); +} +#endif + + +// Allow easy read-only lookup of nested json hierarchies: +// j["one"]["two"]["three"] // undefined; will throw/assert if a hierarchy isn't there +// But: +// nested( j, "one", "two", "three" ) // will not throw; does not copy! +// The result is either a null JSON object or a valid one. The boolean operator can be used as an easy check: +// if( auto inside = nested( j, "one", "two" ) ) +// { ... } +// +class json_ref +{ + json const & _j; public: - json( initializer_list_t init = {} ) : json_type( init ) {} - json( json_type const & j ) : json_type( j ) {} + json_ref() : _j( missing_json ) {} + json_ref( json const & j ) : _j( j ) {} template< typename... Rest > - json( json_type const & j, Rest... rest ) - : json( json::nested( j, std::forward< Rest >( rest )... ) ) + json_ref( json const & j, Rest... rest ) + : _j( _nested_json( j, std::forward< Rest >( rest )... ) ) {} + constexpr bool exists() const noexcept { return ! _j.is_discarded(); } + operator bool() const noexcept { return exists(); } - using json_type::operator=; - + constexpr json const & get_json() const noexcept { return _j; } + operator json const &() const noexcept { return get_json(); } - json_type && moved() { return std::move( *this ); } + constexpr bool is_null() const noexcept { return _j.is_null(); } + constexpr bool is_array() const noexcept { return _j.is_array(); } + constexpr bool is_object() const noexcept { return _j.is_object(); } + constexpr bool is_string() const noexcept { return _j.is_string(); } + constexpr bool is_boolean() const noexcept { return _j.is_boolean(); } + constexpr bool is_number() const noexcept { return _j.is_number(); } + constexpr bool is_number_float() const noexcept { return _j.is_number_float(); } + constexpr bool is_number_integer() const noexcept { return _j.is_number_integer(); } + constexpr bool is_number_unsigned() const noexcept { return _j.is_number_unsigned(); } + constexpr bool is_primitive() const noexcept { return _j.is_primitive(); } + constexpr bool is_structured() const noexcept { return _j.is_structured(); } + bool empty() const { return _j.empty(); } + json::size_type size() const { return _j.size(); } + auto begin() const { return _j.begin(); } + auto end() const { return _j.end(); } - // Returns true if the json has a certain key. - // Does not check the value at all, so it could be any type or null. - static bool has( json_type const & j, json_key const & key ) - { - auto it = j.find( key ); - if( it == j.end() ) - return false; - return true; - } - + std::string dump( const int indent = -1 ) const { return _j.dump( indent ); } - // Returns true if the json has a certain key and its value is not null. - // Does not check the value type. - static bool has_value( json_type const & j, json_key const & key ) + // Dig deeper + template< typename... Rest > + inline json_ref nested( Rest... rest ) const { - auto it = j.find( key ); - if( it == j.end() || it->is_null() ) - return false; - return true; + return _nested_json( _j, std::forward< Rest >( rest )... ); } - - // Get the JSON as a value (copy involved); it must exist - template < class T > - static T value( json_type const & j ) - { - return j.get< T >(); - } - // Get the JSON as a value, or a default if not there (copy involved) - template < class T > - static T value( json_type const & j, T const & default_value ) - { - if( j.is_null() ) - return default_value; - return j.get< T >(); - } - // Get a JSON string by reference (zero copy); it must be a string or it'll throw - static std::string const & string_ref( json_type const & j ) +#if 0 + // Same, but throws + template< typename... Rest > + inline json_ref nested_check( Rest... rest ) const { - return j.get_ref< const json_type::string_t & >(); + if( auto jr = _nested_json( _j, std::forward< Rest >( rest )... ) ) + return jr; + throw std::runtime_error( "key not found: " + _nested_json_path( std::forward< Rest >( rest )... ) ); } +#endif + template< class Key > inline json::const_reference at( Key key ) const { return _j.at( std::forward< Key >( key ) ); } + template< class Key > inline json::const_reference operator[]( Key key ) const { return _j.operator[]( key ); } + template< class T > inline auto get() const { return _j.get< T >(); } + template< class T > inline void get_to( T & value ) const { _j.get_to( value ); } - // If there, gets the value at the given key and returns true; otherwise false. - // Turns json exceptions into runtime errors with additional info. + // If there, gets the value at the given key and returns true; otherwise false template< class T > - static bool get_ex( json_type const & j, json_key const & key, T * pv ) + bool get_ex( T & value ) const { - auto it = j.find( key ); - if( it == j.end() || it->is_null() ) + if( ! exists() ) return false; - try - { - // This will throw for type mismatches, etc. - it->get_to( *pv ); - } - catch( json_type::exception & e ) - { - throw std::runtime_error( "[while getting '" + key + "']" + e.what() ); - } + _j.get_to( value ); return true; } - - // If there, returns the value at the given key; otherwise returns a default value. + // Get the JSON as a value, or a default if not there (throws if wrong type) template< class T > - static T get( json_type const & j, json_key const & key, T const & default_value ) + constexpr T default_value( T const & default_value ) const noexcept { - if( ! j.is_object() ) - return default_value; - return j.value( key, default_value ); + return exists() ? _j.get< T >() : default_value; } + // Get the object, with a default being an empty one; does not throw + inline constexpr json const & default_object() const noexcept { return is_object() ? _j : empty_json_object; } - // If there, returns the value at the given key; otherwise throws! - // Turns json exceptions into runtime errors with additional info. - template< class T > - static T get( json_type const & j, json_key const & key ) - { - // This will throw for type mismatches, etc. - // Does not check for existence: will throw, too! - return j.at(key).get< T >(); - } + // Get the string object, with a default being an empty one; does not throw + inline constexpr json const & default_string() const noexcept { return is_string() ? _j : empty_json_string; } + // Get a JSON string by reference (zero copy); it must be a string or it'll throw + inline std::string const & string_ref() const { return _j.get_ref< const json::string_t & >(); } - // If there, returns the value at the given index (in an array); otherwise throws! - // Turns json exceptions into runtime errors with additional info. - template< class T > - static T get( json_type const & j, int index ) - { - // This will throw for type mismatches, etc. - // Does not check for existence: will throw, too! - return j.at( index ).get< T >(); - } + // Get a JSON string by reference (zero copy); does not throw + inline std::string const & string_ref_or_empty() const { return default_string().get_ref< const json::string_t & >(); } +}; - // If there, returns the value at the given iterator; otherwise throws! - // Turns json exceptions into runtime errors with additional info. - template < class T > - static T get( json_type const & j, json_type::const_iterator const & it ) - { - if( it == j.end() ) - throw std::runtime_error( "unexpected end of json" ); - // This will throw for type mismatches, etc. - // Does not check for existence: will throw, too! - return it->get< T >(); - } +inline std::ostream & operator<<( std::ostream & os, json_ref const & j ) +{ + return operator<<( os, static_cast< json const & >( j ) ); +} - template< typename... Rest > - static json_ref nested( json_type const & j ) { return j; } - template< typename... Rest > - static json_ref nested( json_type const & j, json_key const & inner, Rest... rest ) - { - auto it = j.find( inner ); - if( it == j.end() ) - return null_json; - return nested( *it, std::forward< Rest >( rest )... ); - } - template< typename... Rest > - static json_ref nested( json_type const & j, size_type index, Rest... rest ) - { - if( ! j.is_array() ) - return null_json; - if( index >= j.size() ) - return null_json; - return nested( j[index], std::forward< Rest >( rest )... ); - } +template< typename... Rest > +json_ref _nested_json( json const & j ) +{ + // j.nested() + return j; +} +template< typename... Rest > +json_ref _nested_json( json const & j, bool ( json::*is_fn )() const ) +{ + // j.nested( &json::is_string ) + if( ! ( j.*is_fn )() ) + return missing_json; + return j; +} +template< typename... Rest > +json_ref _nested_json( json const & j, json_key const & inner, Rest... rest ) +{ + // j.nested( "key", ... ) + auto it = j.find( inner ); + if( it == j.end() ) + return missing_json; + return _nested_json( *it, std::forward< Rest >( rest )... ); +} +template< typename... Rest > +json_ref _nested_json( json const & j, json::size_type index, Rest... rest ) +{ + // j.nested( index, ... ) + if( ! j.is_array() ) + return missing_json; + if( index >= j.size() ) + return missing_json; + return _nested_json( j[index], std::forward< Rest >( rest )... ); +} - static inline std::string nested_path( json_key const & a ) { return a; } - static inline std::string nested_path( size_type const & x ) { return '[' + std::to_string( x ) + ']'; } - template< typename... Rest > - static std::string nested_path( json_key const & a, json_key const & b, Rest... rest ) - { - return a + '/' + nested_path( b, std::forward< Rest >( rest )... ); - } - template< typename... Rest > - static std::string nested_path( json_key const & a, size_type b, Rest... rest ) - { - return a + nested_path( b, std::forward< Rest >( rest )... ); - } - template< typename... Rest > - static std::string nested_path( size_type a, size_type b, Rest... rest ) - { - return nested_path( a ) + nested_path( b, std::forward< Rest >( rest )... ); - } - template< typename... Rest > - static std::string nested_path( size_type a, json_key const & b, Rest... rest ) - { - return nested_path( a ) + '/' + nested_path(b, std::forward< Rest >(rest)...); - } - template< typename... Rest > - static std::string nested_path( size_type index, Rest... rest ) - { - return a + '[' + std::to_string(index) + ']' + nested_path(std::forward< Rest >(rest)...); - } +// Since we know how we're derived from, we can get the json, or the reference to it, from this: +// json <- nlohmann::basic_json<...> <- json_base +inline json_ref json_base::_ref() const +{ + return static_cast< json const & >( *this ); +} - template< typename... Rest > - inline json_ref find( Rest... rest ) const - { - return json_ref( *this, std::forward< Rest >( rest )... ); - } - template< typename... Rest > - inline json_ref at( Rest... rest ) const - { - return json_ref( *this ).at( std::forward< Rest >( rest )... ); - } +// Returns false if the object wasn't found +inline bool json_base::exists() const +{ + return _ref().exists(); +} - // Recursively patches existing 'j' with contents of 'patches', which must be a JSON object. - // A 'null' value inside erases previous contents. Any other value overrides. - // See: https://json.nlohmann.me/api/basic_json/merge_patch/ - // Example below, for load_app_settings. - // Use 'what' to denote what it is we're patching in, if a failure happens. The std::runtime_error will populate with - // it. - // - static void patch( json_type & j, json_type const & patches, std::string const & what = {} ); - - - // Loads configuration settings from 'global' content. - // E.g., a configuration file may contain: - // { - // "context": { - // "dds": { - // "enabled": false, - // "domain" : 5 - // } - // }, - // ... - // } - // This function will load a specific key 'context' inside and return it. The result will be a disabling of dds: - // Besides this "global" key, application-specific settings can override the global settings, e.g.: - // { - // "context": { - // "dds": { - // "enabled": false, - // "domain" : 5 - // } - // }, - // "realsense-viewer": { - // "context": { - // "dds": { "enabled": null } - // } - // }, - // ... - // } - // If the current application is 'realsense-viewer', then the global 'context' settings will be patched with the - // application-specific 'context' and returned: - // { - // "dds": { - // "domain" : 5 - // } - // } - // See rules for patching in patch(). - // The 'application' is usually any single-word executable name (without extension). - // The 'subkey' is mandatory. - // The 'error_context' is used for error reporting, to show what failed. Like application, it should be a single word - // that can be used to denote hierarchy within the global json. - // - static json_type load_app_settings( json_type const & global, - std::string const & application, - json_key const & subkey, - std::string const & error_context ); - - - // Same as above, but automatically takes the application name from the executable-name. - // - static json_type load_settings( json_type const & global, - json_key const & subkey, - std::string const & error_context ); +// Get the JSON as a value, or a default if not there (throws if wrong type) +template< class T > +inline T json_base::default_value( T const & default_value ) const +{ + return _ref().default_value( default_value ); +} +// If there, gets the value at the given key and returns true; otherwise false +template< class T > +inline bool json_base::get_ex( T & value ) const +{ + return _ref().get_ex( value ); +} -}; +// Get the object, with a default being an empty one; does not throw +inline json_ref json_base::default_object() const +{ + return _ref().default_object(); +} + +// Get the string object, with a default being an empty one; does not throw +inline json_ref json_base::default_string() const +{ + return _ref().default_string(); +} +// Get a JSON string by reference (zero copy); it must be a string or it'll throw +inline std::string const & json_base::string_ref() const +{ + return _ref().string_ref(); +} + +// Get a JSON string by reference (zero copy); does not throw +inline std::string const & json_base::string_ref_or_empty() const +{ + return default_string().string_ref(); +} // Allow easy read-only lookup of nested json hierarchies: // j["one"]["two"]["three"] // undefined; will throw/assert if a hierarchy isn't there @@ -273,85 +252,17 @@ class json : public json_type // if( auto inside = nested( j, "one", "two" ) ) // { ... } // -class json_ref -{ - json_type const & _j; - -public: - json_ref() : _j( null_json ) {} - json_ref( json_type const & j ) : _j( j ) {} - - template< typename... Rest > - json_ref( json_type const & j, Rest... rest ) - : _j( json::nested( j, std::forward< Rest >( rest )... ) ) - {} - - //json_type const * operator->() const { return &_j; } - - bool exists() const { return ! _j.is_null(); } - operator bool() const { return exists(); } - - json_type const & get_json() const { return _j; } - operator json_type const & () const { return get_json(); } - operator json const &() const { return static_cast< json const & >( _j ); } - - bool is_null() const { return _j.is_null(); } - bool is_array() const { return _j.is_array(); } - bool is_object() const { return _j.is_object(); } - bool is_string() const { return _j.is_string(); } - bool is_number() const { return _j.is_number(); } - bool is_number_float() const { return _j.is_number_float(); } - bool is_number_integer() const { return _j.is_number_integer(); } - bool is_number_unsigned() const { return _j.is_number_unsigned(); } - - bool empty() const { return _j.empty(); } - json_type::size_type size() const { return _j.size(); } - auto begin() const { return _j.begin(); } - auto end() const { return _j.end(); } - - // Dig deeper - template< typename... Rest > - inline json_ref find( Rest... rest ) const - { - return json::nested( _j, std::forward< Rest >( rest )... ); - } - - // Same, but throws - template< typename... Rest > - inline json_ref at( Rest... rest ) const - { - if( auto jr = json::nested( _j, std::forward< Rest >( rest )... ) ) - return jr; - throw std::runtime_error( "key not found: " + json::nested_path( std::forward< Rest >( rest )... ) ); - } - inline json_ref operator[]( json_key const & key ) const { return find( key ); } - - // Get the JSON as a value - template< class T > T value() const { return json::value< T >( _j ); } - // Get the JSON as a value, put into pre-allocated variable - template< class T > T & value_to( T & t ) const { return _j.get_to< T >( t ); } - // Get the JSON as a value, or a default if not there (throws if wrong type) - template < class T > T default_value( T const & default_value ) const { return json::value< T >( _j, default_value ); } - // Get the object, with a default being an empty one; does not throw - json_type const & default_object() const { return is_object() ? _j : empty_json_object; } - // Get the object, with a default being an empty one; does not throw - json_type const & default_string() const { return is_string() ? _j : empty_json_string; } - // Get a JSON string by reference (zero copy); it must be a string or it'll throw - inline std::string const & string_ref() const { return json::string_ref( _j ); } - // Get a JSON string by reference (zero copy); does not throw - inline std::string const & string_ref_or_empty() const { return json::string_ref( default_string() ); } -}; - - -inline std::ostream & operator<<( std::ostream & os, json const & j ) +template< typename... Rest > +inline json_ref json_base::nested( Rest... rest ) const { - return operator<<( os, static_cast< json_type const & >( j ) ); + return _ref().nested( std::forward< Rest >( rest )... ); } - -inline std::ostream & operator<<( std::ostream & os, json_ref const & j ) +// Same, but throws +template< typename... Rest > +inline json_ref json_base::nested_check( Rest... rest ) const { - return operator<<( os, static_cast< json_type const & >( j ) ); + return _ref().nested_check( std::forward< Rest >( rest )... ); } diff --git a/third-party/rsutils/include/rsutils/string/hexarray.h b/third-party/rsutils/include/rsutils/string/hexarray.h index 9a5f45dc3c7..aa70d8fe98f 100644 --- a/third-party/rsutils/include/rsutils/string/hexarray.h +++ b/third-party/rsutils/include/rsutils/string/hexarray.h @@ -1,9 +1,8 @@ // License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2023 Intel Corporation. All Rights Reserved. - #pragma once -#include +#include #include #include @@ -37,7 +36,7 @@ class hexarray private: bytes _bytes; - friend void from_json( nlohmann::json const &, hexarray & ); + friend void from_json( rsutils::json const &, hexarray & ); public: hexarray() = default; @@ -72,9 +71,9 @@ inline std::ostream & operator<<( std::ostream & os, hexarray const & hexa ) // Allow j["key"] = hexarray( bytes ); -void to_json( nlohmann::json &, const hexarray & ); +void to_json( rsutils::json &, const hexarray & ); // Allow j.get< hexarray >(); -void from_json( nlohmann::json const &, hexarray & ); +void from_json( rsutils::json const &, hexarray & ); // See https://github.com/nlohmann/json#arbitrary-types-conversions diff --git a/third-party/rsutils/py/pyrsutils.cpp b/third-party/rsutils/py/pyrsutils.cpp index 7d0ce2c5147..3b29223eead 100644 --- a/third-party/rsutils/py/pyrsutils.cpp +++ b/third-party/rsutils/py/pyrsutils.cpp @@ -40,7 +40,7 @@ PYBIND11_MODULE(NAME, m) { py::arg( "max-length" ) = 96 ); m.def( "shorten_json_string", - []( nlohmann::json const & j, size_t max_length ) + []( rsutils::json const & j, size_t max_length ) { return rsutils::string::shorten_json_string( j.dump(), max_length ).to_string(); }, py::arg( "json" ), py::arg( "max-length" ) = 96 ); diff --git a/third-party/rsutils/src/hexarray.cpp b/third-party/rsutils/src/hexarray.cpp index 85f584426fd..46e208348be 100644 --- a/third-party/rsutils/src/hexarray.cpp +++ b/third-party/rsutils/src/hexarray.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include namespace rsutils { @@ -25,32 +25,32 @@ namespace string { } -void to_json( nlohmann::json & j, const hexarray & hexa ) +void to_json( rsutils::json & j, const hexarray & hexa ) { j = hexa.to_string(); } -void from_json( nlohmann::json const & j, hexarray & hexa ) +void from_json( rsutils::json const & j, hexarray & hexa ) { if( j.is_array() ) { hexa._bytes.resize( j.size() ); std::transform( j.begin(), j.end(), std::begin( hexa._bytes ), - []( nlohmann::json const & elem ) + []( rsutils::json const & elem ) { if( ! elem.is_number_unsigned() ) - throw nlohmann::json::type_error::create( 302, "array value not an unsigned integer", &elem ); + throw rsutils::json::type_error::create( 302, "array value not an unsigned integer", &elem ); auto v = elem.template get< uint64_t >(); if( v > 255 ) - throw nlohmann::json::out_of_range::create( 401, "array value out of range", &elem ); + throw rsutils::json::out_of_range::create( 401, "array value out of range", &elem ); return uint8_t( v ); } ); } else if( j.is_string() ) hexa = hexarray::from_string( j.get< std::string >() ); else - throw nlohmann::json::type_error::create( 317, "hexarray should be a string", &j ); + throw rsutils::json::type_error::create( 317, "hexarray should be a string", &j ); } diff --git a/third-party/rsutils/src/json.cpp b/third-party/rsutils/src/json.cpp index fdb71edb27e..617af45befe 100644 --- a/third-party/rsutils/src/json.cpp +++ b/third-party/rsutils/src/json.cpp @@ -2,17 +2,32 @@ // Copyright(c) 2023 Intel Corporation. All Rights Reserved. #include +#include #include +#include + + namespace rsutils { -json_type const null_json = {}; -json_type const empty_json_string = json_type::value_type( json_type::value_t::string ); -json_type const empty_json_object = json_type::object(); +json const null_json = {}; +json const missing_json = json::value_t::discarded; +json const empty_json_string = json::value_t::string; +json const empty_json_object = json::object(); -/*static*/ void json::patch( json_type & j, json_type const & patches, std::string const & what ) +// Recursively patches existing JSON with contents of 'overrides', which must be a JSON object. +// A 'null' value inside erases previous contents. Any other value overrides. +// See: https://json.nlohmann.me/api/basic_json/merge_patch/ +// Example below, for load_app_settings. +// Use 'what' to denote what it is we're patching in, if a failure happens. The std::runtime_error will populate with +// it. +// +// NOTE: called 'override' to agree with terminology elsewhere, and to avoid collisions with the json patch(), +// merge_patch(), existing functions. +// +void json_base::override( json_ref patches, std::string const & what ) { if( ! patches.is_object() ) { @@ -22,7 +37,7 @@ json_type const empty_json_object = json_type::object(); try { - j.merge_patch( patches ); + static_cast< json * >( this )->merge_patch( patches ); } catch( std::exception const & e ) { @@ -32,26 +47,94 @@ json_type const empty_json_object = json_type::object(); } -/*static*/ json_type json::load_app_settings( json_type const & global, - std::string const & application, - json_key const & subkey, - std::string const & error_context ) +// Load the contents of a file into a JSON object. +// +// Throws if any errors are encountered with the file or its contents. +// Returns the contents. If the file wasn't there, returns missing_json. +// +json json_config::load_from_file( std::string const & filename ) +{ + std::ifstream f( filename ); + if( f.good() ) + { + try + { + return json::parse( f ); + } + catch( std::exception const & e ) + { + throw std::runtime_error( "failed to load configuration file (" + filename + + "): " + std::string( e.what() ) ); + } + } + return missing_json; +} + + + +// Loads configuration settings from 'global' content (loaded by load_from_file()?). +// E.g., a configuration file may contain: +// { +// "context": { +// "dds": { +// "enabled": false, +// "domain" : 5 +// } +// }, +// ... +// } +// This function will load a specific key 'context' inside and return it. The result will be a disabling of dds: +// Besides this "global" key, application-specific settings can override the global settings, e.g.: +// { +// "context": { +// "dds": { +// "enabled": false, +// "domain" : 5 +// } +// }, +// "realsense-viewer": { +// "context": { +// "dds": { "enabled": null } +// } +// }, +// ... +// } +// If the current application is 'realsense-viewer', then the global 'context' settings will be patched with the +// application-specific 'context' and returned: +// { +// "dds": { +// "domain" : 5 +// } +// } +// See rules for patching in patch(). +// The 'application' is usually any single-word executable name (without extension). +// The 'subkey' is mandatory. +// The 'error_context' is used for error reporting, to show what failed. Like application, it should be a single word +// that can be used to denote hierarchy within the global json. +// +json json_config::load_app_settings( json const & global, + std::string const & application, + json_key const & subkey, + std::string const & error_context ) { // Take the global subkey settings out of the configuration - nlohmann::json settings; - if( auto global_subkey = json_ref( global, subkey ) ) - patch( settings, global_subkey, "global " + error_context + '/' + subkey ); + json settings; + if( auto global_subkey = global.nested( subkey ) ) + settings.override( global_subkey, "global " + error_context + '/' + subkey ); // Patch any application-specific subkey settings - if( auto application_subkey = json_ref( global, application, subkey ) ) - patch( settings, application_subkey, error_context + '/' + application + '/' + subkey ); + if( auto application_subkey = global.nested( application, subkey ) ) + settings.override( application_subkey, error_context + '/' + application + '/' + subkey ); return settings; } -/*static*/ json_type -json::load_settings( json_type const & global, json_key const & subkey, std::string const & error_context ) +// Same as above, but automatically takes the application name from the executable-name +// +json json_config::load_settings( json const & global, + json_key const & subkey, + std::string const & error_context ) { return load_app_settings( global, rsutils::os::executable_name(), subkey, error_context ); } diff --git a/tools/dds/dds-adapter/lrs-device-controller.cpp b/tools/dds/dds-adapter/lrs-device-controller.cpp index 218cf7ba9e1..c00b1f7419c 100644 --- a/tools/dds/dds-adapter/lrs-device-controller.cpp +++ b/tools/dds/dds-adapter/lrs-device-controller.cpp @@ -630,7 +630,7 @@ bool lrs_device_controller::on_open_streams( rsutils::json const & control, rsut // out, a sensor is reset back to its default state using implicit stream selection. // (For example, the 'Stereo Module' sensor controls Depth, IR1, IR2: but turning on all 3 has performance // implications and may not be desirable. So you can open only Depth and IR1/2 will stay inactive...) - if( rsutils::json::get< bool >( control, "reset", true ) ) + if( control.nested( "reset" ).default_value( true ) ) _bridge.reset(); auto const & msg_profiles = control["stream-profiles"]; @@ -653,7 +653,7 @@ bool lrs_device_controller::on_open_streams( rsutils::json const & control, rsut } // We're here so all the profiles were acceptable; lock them in -- with no implicit profiles! - if( rsutils::json::get< bool >( control, "commit", true ) ) + if( control.nested( "commit" ).default_value( true ) ) _bridge.commit(); // We don't touch the reply - it's already filled in for us @@ -870,22 +870,22 @@ bool lrs_device_controller::on_hwm( rsutils::json const & control, rsutils::json rsutils::string::hexarray data; uint32_t opcode; - if( rsutils::json::get_ex( control, "opcode", &opcode ) ) + if( control.nested( "opcode" ).get_ex( opcode ) ) { // In the presence of 'opcode', we're asked to build the command using optional parameters uint32_t param1 = 0, param2 = 0, param3 = 0, param4 = 0; - rsutils::json::get_ex( control, "param1", ¶m1 ); - rsutils::json::get_ex( control, "param2", ¶m2 ); - rsutils::json::get_ex( control, "param3", ¶m3 ); - rsutils::json::get_ex( control, "param4", ¶m4 ); + control.nested( "param1" ).get_ex( param1 ); + control.nested( "param2" ).get_ex( param2 ); + control.nested( "param3" ).get_ex( param3 ); + control.nested( "param4" ).get_ex( param4 ); - rsutils::json::get_ex( control, "data", &data ); // optional + control.nested( "data" ).get_ex( data ); // optional // Build the HWM command data = dp.build_command( opcode, param1, param2, param3, param4, data.get_bytes() ); // And, if told to not actually run it, we return the HWM command - if( rsutils::json::get< bool >( control, "build-command", false ) ) + if( control.nested( "build-command" ).default_value( false ) ) { reply["data"] = data; return true; @@ -893,7 +893,7 @@ bool lrs_device_controller::on_hwm( rsutils::json const & control, rsutils::json } else { - if( ! rsutils::json::get_ex( control, "data", &data ) ) + if( ! control.nested( "data" ).get_ex( data ) ) throw std::runtime_error( "no 'data' in HWM control" ); } diff --git a/tools/dds/dds-adapter/rs-dds-adapter.cpp b/tools/dds/dds-adapter/rs-dds-adapter.cpp index 91a071d751b..c125545488f 100644 --- a/tools/dds/dds-adapter/rs-dds-adapter.cpp +++ b/tools/dds/dds-adapter/rs-dds-adapter.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -48,7 +49,7 @@ std::string get_topic_root( std::string const & name, std::string const & serial topics::device_info rs2_device_to_info( rs2::device const & dev ) { - nlohmann::json j; + rsutils::json j; // Name is mandatory std::string const name = dev.get_info( RS2_CAMERA_INFO_NAME ); @@ -74,35 +75,24 @@ topics::device_info rs2_device_to_info( rs2::device const & dev ) } -static nlohmann::json load_settings( nlohmann::json const & local_settings ) +static rsutils::json load_settings( rsutils::json const & local_settings ) { - nlohmann::json config; - // Load the realsense configuration file settings - std::ifstream f( rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + RS2_CONFIG_FILENAME ); - if( f.good() ) - { - try - { - config = nlohmann::json::parse( f ); - } - catch( std::exception const & e ) - { - throw std::runtime_error( "failed to load configuration file: " + std::string( e.what() ) ); - } - } + std::string const filename = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + RS2_CONFIG_FILENAME; + auto config = rsutils::json_config::load_from_file( filename ); - config = rsutils::json::load_settings( config, "context", "config-file" ); + // Take just the 'context' part + config = rsutils::json_config::load_settings( config, "context", "config-file" ); // Take the "dds" settings only - config = rsutils::json::nested( config, "dds" ); + config = config.nested( "dds" ); // We should always have DDS enabled if( config.is_object() ) config.erase( "enabled" ); // Patch the given local settings into the configuration - rsutils::json::patch( config, local_settings, "local settings" ); + config.override( local_settings, "local settings" ); return config; } @@ -157,7 +147,7 @@ try // Create a DDS participant auto participant = std::make_shared< dds_participant >(); { - nlohmann::json dds_settings = nlohmann::json::object(); + rsutils::json dds_settings = rsutils::json::object(); dds_settings = load_settings( dds_settings ); participant->init( domain, "rs-dds-adapter", std::move( dds_settings ) ); } @@ -173,7 +163,7 @@ try std::cout << "Start listening to RS devices.." << std::endl; // Create a RealSense context - nlohmann::json j = { + rsutils::json j = { { "dds", false }, // Don't discover DDS devices from the network, we want local devices only { "format-conversion", "basic" } // Don't convert raw sensor formats (except interleaved) will be done by receiver }; diff --git a/tools/dds/dds-sniffer/rs-dds-sniffer.cpp b/tools/dds/dds-sniffer/rs-dds-sniffer.cpp index 360b0a3e521..064ac7ed9ed 100644 --- a/tools/dds/dds-sniffer/rs-dds-sniffer.cpp +++ b/tools/dds/dds-sniffer/rs-dds-sniffer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace TCLAP; using namespace eprosima::fastdds::dds; @@ -194,35 +195,24 @@ dds_sniffer::~dds_sniffer() } -static nlohmann::json load_settings( nlohmann::json const & local_settings ) +static rsutils::json load_settings( rsutils::json const & local_settings ) { - nlohmann::json config; - // Load the realsense configuration file settings - std::ifstream f( rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + "realsense-config.json" ); - if( f.good() ) - { - try - { - config = nlohmann::json::parse( f ); - } - catch( std::exception const & e ) - { - throw std::runtime_error( "failed to load configuration file: " + std::string( e.what() ) ); - } - } + std::string const filename = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) + "realsense-config.json"; + auto config = rsutils::json_config::load_from_file( filename ); - config = rsutils::json::load_settings( config, "context", "config-file" ); + // Take just the 'context' part + config = rsutils::json_config::load_settings( config, "context", "config-file" ); // Take the "dds" settings only - config = rsutils::json::nested( config, "dds" ); + config = config.nested( "dds" ); // We should always have DDS enabled if( config.is_object() ) config.erase( "enabled" ); // Patch the given local settings into the configuration - rsutils::json::patch( config, local_settings, "local settings" ); + config.override( local_settings, "local settings" ); return config; } @@ -254,7 +244,7 @@ bool dds_sniffer::init( realdds::dds_domain_id domain ) on_type_discovery( topic_name, dyn_type ); } ); - nlohmann::json settings( nlohmann::json::object() ); + rsutils::json settings( rsutils::json::object() ); settings = load_settings( settings ); _participant.init( domain, rsutils::os::executable_name(), std::move( settings ) ); diff --git a/tools/enumerate-devices/rs-enumerate-devices.cpp b/tools/enumerate-devices/rs-enumerate-devices.cpp index d2c1ede52a1..1daf7f55a6e 100644 --- a/tools/enumerate-devices/rs-enumerate-devices.cpp +++ b/tools/enumerate-devices/rs-enumerate-devices.cpp @@ -11,8 +11,8 @@ #include #include -#include -using nlohmann::json; +#include +using rsutils::json; #include "tclap/CmdLine.h" diff --git a/tools/realsense-viewer/realsense-viewer.cpp b/tools/realsense-viewer/realsense-viewer.cpp index 29ed3c05a72..3715542b969 100644 --- a/tools/realsense-viewer/realsense-viewer.cpp +++ b/tools/realsense-viewer/realsense-viewer.cpp @@ -332,7 +332,7 @@ int main(int argc, const char** argv) try std::shared_ptr device_models = std::make_shared(); - nlohmann::json settings = nlohmann::json::object(); + rsutils::json settings = rsutils::json::object(); if( only_sw_arg.getValue() ) { #if defined( BUILD_WITH_DDS ) diff --git a/tools/terminal/rs-terminal.cpp b/tools/terminal/rs-terminal.cpp index 4eb62800b8f..800318194a0 100644 --- a/tools/terminal/rs-terminal.cpp +++ b/tools/terminal/rs-terminal.cpp @@ -12,7 +12,7 @@ #include "parser.hpp" #include "auto-complete.h" -#include +#include #include @@ -197,9 +197,9 @@ int main(int argc, char** argv) // parse command.xml rs2::log_to_file(RS2_LOG_SEVERITY_WARN, "librealsense.log"); - nlohmann::json settings; + rsutils::json settings; #ifdef BUILD_WITH_DDS - nlohmann::json dds; + rsutils::json dds; if( domain_arg.isSet() ) dds["domain"] = domain_arg.getValue(); if( only_sw_arg.isSet() ) diff --git a/unit-tests/dds/test-control-reply.py b/unit-tests/dds/test-control-reply.py index 45d3b05c9ea..bc1f505561b 100644 --- a/unit-tests/dds/test-control-reply.py +++ b/unit-tests/dds/test-control-reply.py @@ -11,21 +11,6 @@ device_info = dds.message.device_info() device_info.topic_root = 'server/device' - -with test.closure( 'Start the server participant' ): - participant = dds.participant() - participant.init( 123, 'server' ) - -with test.closure( 'Create the server' ): - device_info.name = 'Some device' - s1p1 = dds.video_stream_profile( 9, dds.video_encoding.rgb, 10, 10 ) - s1profiles = [s1p1] - s1 = dds.color_stream_server( 's1', 'sensor' ) - s1.init_profiles( s1profiles, 0 ) - server = dds.device_server( participant, device_info.topic_root ) - server.init( [s1], [], {} ) - - with test.remote.fork( nested_indent=None ) as remote: if remote is None: # we're the fork @@ -51,7 +36,7 @@ def _on_control( server, id, control, reply ): reply['sequence'] = n_replies # to show that we've processed it reply['nested-json'] = { 'more': True } # to show off return True # otherwise the control will be flagged as error - server.on_control( _on_control ) + subscription = server.on_control( _on_control ) raise StopIteration() # exit the 'with' statement diff --git a/unit-tests/rsutils/string/test-hexarray.cpp b/unit-tests/rsutils/string/test-hexarray.cpp index 2c84a00317e..ad7a79ff7f4 100644 --- a/unit-tests/rsutils/string/test-hexarray.cpp +++ b/unit-tests/rsutils/string/test-hexarray.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include using rsutils::string::hexdump; @@ -399,30 +399,30 @@ TEST_CASE( "hexarray", "[hexarray]" ) } SECTION( "to json" ) { - nlohmann::json j; + rsutils::json j; j["blah"] = hexarray( std::move( ba ) ); CHECK( j.dump() == "{\"blah\":\"00010203040506070809\"}"); } SECTION( "(same as using hexarray::to_string)" ) { - nlohmann::json j; + rsutils::json j; j["blah"] = hexarray::to_string( ba ); CHECK( j.dump() == "{\"blah\":\"00010203040506070809\"}" ); } SECTION( "from json" ) { - auto j = nlohmann::json::parse( "{\"blah\":\"00010203040506070809\"}" ); + auto j = rsutils::json::parse( "{\"blah\":\"00010203040506070809\"}" ); CHECK( j["blah"].get< hexarray >().get_bytes() == ba ); } SECTION( "from json shuld accept bytearrays, too" ) { - auto j = nlohmann::json::parse( "{\"blah\":[0,1,2,3,4,5,6,7,8,9]}" ); + auto j = rsutils::json::parse( "{\"blah\":[0,1,2,3,4,5,6,7,8,9]}" ); CHECK( j["blah"].get< hexarray >().get_bytes() == ba ); - j = nlohmann::json::parse( "{\"blah\":[0,1,256]}" ); // out-of-range + j = rsutils::json::parse( "{\"blah\":[0,1,256]}" ); // out-of-range CHECK_THROWS( j["blah"].get< hexarray >().get_bytes() ); - j = nlohmann::json::parse( "{\"blah\":[0,1,2.0]}" ); // must be integer + j = rsutils::json::parse( "{\"blah\":[0,1,2.0]}" ); // must be integer CHECK_THROWS( j["blah"].get< hexarray >().get_bytes() ); } } diff --git a/wrappers/python/pyrs_context.cpp b/wrappers/python/pyrs_context.cpp index cd56f62558f..a8eb15fdbb7 100644 --- a/wrappers/python/pyrs_context.cpp +++ b/wrappers/python/pyrs_context.cpp @@ -18,7 +18,7 @@ void init_context(py::module &m) { py::class_< rs2::context >( m, "context", "Librealsense context class. Includes realsense API version." ) .def( py::init< char const * >(), py::arg( "json-settings" ) = nullptr ) - .def( py::init<>( []( nlohmann::json const & j ) { return rs2::context( j.dump() ); } ), py::arg( "json-settings" ) ) + .def( py::init<>( []( rsutils::json const & j ) { return rs2::context( j.dump() ); } ), py::arg( "json-settings" ) ) .def("query_devices", (rs2::device_list(rs2::context::*)() const) &rs2::context::query_devices, "Create a static" " snapshot of all connected devices at the time of the call.") .def( "query_devices", ( rs2::device_list( rs2::context::* )(int) const ) & rs2::context::query_devices, "Create a static"