Json Serialization

Configuration also supports serialization to and from json format via functions toJSON and fromJSON respectively, so that you can save your configuration into a file and load it as needed. For the json itself we use nlohman::json library.

Format Description

The configuration description consists of three main parts: modules, moduleJoints, and spaceJoints.

The minimal configuration looks like this.

#include <configuration/serialization.hpp>

// the json library supports string literals
auto js = "{ \"modules\" : [], \"spaceJoints\" : [], \"moduleJoints\" : [] }"_json;
RofiWorld world = fromJSON( js );
// and we can continue as before

If we were to represent the configuration with two universal modules shown above, we could do it with this json

{
    "modules" : [
        {
            "id" : 42,
            "type" : "universal",
            "alpha" : 0,
            "beta"  : 0,
            "gamma" : 0
        },
        {
            "id" : 66,
            "type" : "universal",
            "alpha" : 0,
            "beta"  : 45,
            "gamma" : 90
        }
    ],

    "moduleJoints" : [
        {
            "orientation" : "East",
            "from" : { "id" : 66, "connector" : "A+X" },
            "to" :   { "id" : 42, "connector" : "A-X" }
        }
    ],

    "spaceJoints" : [
        {
            "point" : [ 0, 0, 0 ],
            "to" : {
                     "id" : 42,
                     "component" : 6
            },
            "joint" : {
                        "type" : "rigid",
                        "sourceToDestination" : [ [1, 0, 0, 0]
                                                , [0, 1, 0, 0]
                                                , [0, 0, 1, 0]
                                                , [0, 0, 0, 1] ]
                    }
        }
    ]
}

You are not limited to universal modules only, currently we support a module Pad representing a 5x4 pad of RoFICoMs which can be represented as

{
    "id" : 66,
    "type"   : "pad",
    "width"  : 5,
    "height" : 4
}

and there is also a representation of an arbitrary module corresponding to the UnknownModule. Its attributes mirror the class

{
    "id" : 66,
    "components" : [ < array of components > ],
    "joints"     : [ < array of joints >     ]
}

where the component has three possible values

[
    {
        "type" : "roficom"
    },
    {
        "type" : "UM shoe"
    },
    {
        "type" : "UM body"
    }
]

and joint is represented as

{
    "from" : < component >,
    "destination" : < component >,
    "sourceToDestination" : < matrix >,
    "joint" : < joint >
}

where possible values of the joint are either RigidJoint represented as

{
    "type" : "rigid"
}

or the RotationJoint which requires appropriate matrices

{
    "type" : "rotational",
    "axis" : < 4-dimensional array >,
    "preMatrix"  : < matrix >,
    "postMatrix" : < matrix >,
    "min" : < lower-limit - number >,
    "max" : < upper-limit - number >
}

Matrices are, as shown above, represented by 4x4 dimensional array. Or, for the identity matrix, you can use a string representation, just write “identity” instead of [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]].

Functions

inline nlohmann::json rofi::configuration::serialization::toJSON(const RofiWorld &world)
template<typename Callback>
inline nlohmann::json rofi::configuration::serialization::toJSON(const RofiWorld &world, Callback attrCb)

Serialize given RofiWorld to json.

Callback for attributes has to cover all of these possible arguments:

  • callback for a module gets: const reference to the Module itself

  • callback for a joint gets: const reference to the Joint itself and its index within the module

  • callback for a component gets: const reference to the Component or ComponentJoint and its index

  • callback for a SpaceJoint or a RoficomJoint gets a const reference to the appropriate joint

Parameters

attrCb – a suitable function or Callable object that returns a nlohmann::json which is then stored into appropriate "attributes" field

The callback is optional. It provides you with the ability to extend the json representation with an “attributes” property, which can be added to any object within the json. It can contain some metadata you might use when working with and sharing the configuration description. For details, see the section below.

inline RofiWorld rofi::configuration::serialization::fromJSON(const nlohmann::json &j)
template<typename Callback>
inline RofiWorld rofi::configuration::serialization::fromJSON(const nlohmann::json &j, Callback attrCb)

Load a RofiWorld from given json.

Callback for attributes has to cover all of these possible arguments:

  • callback for a Module gets: const reference for the json[ “attributes” ] and const reference to the Module itself

  • callback for a Joint gets: const reference for the json[ “attributes” ] and const reference to the Joint itself and its index within the module

  • callback for a Component and ComponentJoint gets: const reference for the json[ “attributes” ] and a const reference to the Component itself and its index within the module

  • callback for a SpaceJoint or a RoficomJoint gets: const reference for the json[ “attributes” ] and a handle for given connection

Parameters
  • j – json with a RofiWorld

  • attrCb – a suitable function or Callable object that process appropriate "attributes" fields

Here you can provide a callback function, that is used for parsing the optional “attributes” field. If no callback is provided, the field, if present, is ignored. The callback is written in the same way as for toJSON.

Attributes callback

You can extend the json description of a configuration with “attributes” field. This field can be present in any object within the configuration, so the callback function has to be able to accept every corresponding type. The possible callback for toJSON that stores a ModuleId to “attributes” looks like

overload{ []( const Module& m ) { return nlohmann::json( m.getId() ); },
          []( const ComponentJoint&, int jointIndex ) { return nlohmann::json{}; },
          []( const Component&, int componentIndex  ) { return nlohmann::json{}; },
          []( const RoficomJoint& ) { return nlohmann::json{}; },
          []( const SpaceJoint&   ) { return nlohmann::json{}; }
};

You can see that every function returns a nlohman::json which is then stored to appropriate “attributes” field.

To collect these attributes you can then use this callback

std::vector< ModuleId > ids;

overload{ [ &ids ]( const nlohmann::json& j, const Module& m ) {
                    ids.push_back( j );
          },
          []( const nlohmann::json&, const ComponentJoint&, int jointIndex )  { return; },
          []( const nlohmann::json&, const Component&, int componentIndex  )  { return; },
          []( const nlohmann::json&, RofiWorld::RoficomJointHandle ) { return; },
          []( const nlohmann::json&, RofiWorld::SpaceJointHandle )   { return; },
};

See, that the main difference is in the arguments – callback given to fromJSON takes a json that is the content of the respective “attributes” field.