Enhanced value set

In the previous section, the enhanced value access functionality has been introduced. This functionality allows the user to easily validate their input JSON files by providing comprehensible error messages in case non-defaultable properties are missing or the provided values are not convertible to the expected type. This was achieved by using KeyPath s, special keys and the getValue function.

However, there is nothing like a setValue function, and KeyPath s are not used in to_json functions. This is so because there is no need for validation when converting Tudat objects to JSON. If the process is not successful, it will be the developer’s fault and not the user’s. Additionally, although it is not always possible to access an arbitrary key, since it may be undefined, it is always possible to define any arbitrary key for a nlohmann::json object.

Nonetheless, there are a few functions provided by the json_interface that can make the writing of to_json functions easier, namely:

  • void assignIfNotNaN( nlohmann::json& j, const std::string& key, const EquatableType& value ): updates or defines j[ key ] only if value == value (this comparison returns false for TUDAT_NAN). Can only be used when the comparison operator is defined for EquatableType.
  • void assignIfNotNull( nlohmann::json& j, const std::string& key, const std::shared_ptr< T >& object ): updates or defines j[ key ] only if object != NULL.
  • void assignIfNotEmpty( nlohmann::json& j, const std::string& key, const ContainerType& container ): updates or defines j[ key ] only if object.empty( ) == false. Can only be used when the method empty is defined for ContainerType.

Note that the following is not possible:

nlohmann::json mainJson;
KeyPath keyPath = "integrator" / "type";
mainJson[ keyPath ] = "rungeKutta4";             // compile error
mainJson[ "integrator.type" ] = "rungeKutta4";   // wrong! {"integrator.type":"rungeKutta4"}

Instead, the default basic value set methods have to be used:

nlohmann::json mainJson;
mainJson[ "integrator" ][ "type" ] = "rungeKutta4";

If one wants to use the properties of a given object to update the keys of a nlohmann::json object that is above in the key tree, e.g.:

inline void to_json( nlohmann::json& j, const Integrator& integrator )
{
    j[ "type" ] = integrator.type;
    j[ "stepSize" ] = integrator.stepSize;
    j[ "<-" / "initialEpoch" ] = integrator.initialTime;   // compile error
}

this is not possible (yet). This kind of implementation is required only once in the whole JSON Interface library, when converting a Simulation object to nlohmann::json, as the information contained in the propagator settings is stored in three different places in the simulation object (part of its information is stored in the key propagators of the mainJson, the termination settings are stored in the key termination of the mainJson, and the print interval is stored in the options.printInterval key path of the mainJson). To do so, the following implementation was chosen (code has been simplified):

simulation.h
void to_json( nlohmann::json& mainJson, const Simulation& simulation )
{
    ...
    mainJson[ "integrator" ] = simulation.integrator;
    ...
    propagators::to_json( mainJson, simulation.propagator );
}
propagator.h
namespace propagators
{
    void to_json( nlohmann::json& mainJson, const Propagator& propagator )
    {
        mainJson[ "propagators" ][ 0 ][ "type" ] = propagator.type;
        mainJson[ "propagators" ][ 0 ][ "centralBodies" ] = propagator.centralBodies;
        ...
        mainJson[ "termination" ] = propagator.terminationSettings;
        mainJson[ "options" ][ "printInterval" ] = propagator.printInterval;
    }
}

This is the only case in which a to_json function is manually called in the JSON Interface library. Note that, when passed to the to_json function, the mainJson object is not re-initialised, so the keys defined before this function call are kept.