The previous option for solving the mapping problem between a C++ and json structure turned out to be the first damn thing. Fortunately, development is an iterative process, and there will always be a second behind the first version. Comments (thanks to everyone) and hole analysis in the first pancake helped to improve something.


What was bad


  • it is impossible to use regular C++ structures (including existing ones). All structures must be defined from scratch in a special way
  • Json object can be displayed only on a specially defined structure
  • Json array can only be mapped to a special class
  • impossible to use stl containers
  • macros are simply necessary (you can do without them, but registering methods for setting fields is tightly combined with the initialization of these fields, so without macros the definition of the structure is unreadable)
  • the display is not configurable in any way, i.e. you cannot set, for example, default values ​​or value limits

How it became now


Registration of fields that will participate in the display is no longer tied to the structure. To register, use the function


reg(V T::* ptr, std::string const & name, Options<U>&&... options); 

  • CDMY0CDMY - field pointer
  • CDMY1CDMY - field name
  • CDMY2CDMY - display options

The following types of fields can be used:


  • bool
  • char, unsigned char, short, unsigned short, int unsigned int, long, long long
  • float, double
  • std :: string
  • std :: list
  • std :: vector
  • std :: map (only std :: string can be the key)
  • std :: unordered_map (only std :: string can be the key)
  • std :: multimap (only std :: string can be the key)
  • std :: unordered_multimap (only std :: string can be the key)
  • c++ structures
  • listings

for example


struct Friend { std::string name; std::list<int> counters; }; struct MiB { std::list<Friend> friends; std::vector<std::list<std::string>> groups; std::map<std::string, std::vector<std::string>> books; }; struct_mapping::reg(&Friend::name, "name"); struct_mapping::reg(&Friend::counters, "counters"); struct_mapping::reg(&MiB::friends, "friends"); struct_mapping::reg(&MiB::groups, "groups"); struct_mapping::reg(&MiB::books, "books"); 

After registration, as before, it is called


map_json_to_struct(T & result_struct, std::basic_istream<char> & json_data); 

  • CDMY3CDMY - link to the resulting structure
  • CDMY4CDMY - link to input json data stream

During the display, the correspondence of the field types to the types of the set value is checked, and for numbers, the set value is checked to leave the range of field type values. When types mismatch or if values ​​fall outside the range, exceptions are thrown.


A small complete example looks like this


#include <iostream> #include <sstream> #include "struct_mapping/struct_mapping.h" struct Planet { bool giant; long long surface_area; double mass; std::string satellite; }; int main() { struct_mapping::reg(&Planet::giant, "giant"); struct_mapping::reg(&Planet::surface_area, "surface_area"); struct_mapping::reg(&Planet::mass, "mass"); struct_mapping::reg(&Planet::satellite, "satellite"); Planet earth; std::istringstream json_data(R"json( { "giant": false, "surface_area": 510072000000000, "mass": 5.97237e24, "satellite": "Moon" } )json"); struct_mapping::map_json_to_struct(earth, json_data); std::cout << "earth" << std::endl; std::cout << " giant : " << std::boolalpha << earth.giant << std::endl; std::cout << " surface_area : " << earth.surface_area << std::endl; std::cout << " mass : " << earth.mass << std::endl; std::cout << " satellite : " << earth.satellite << std::endl; } 

what results in


earth giant : false surface_area : 510072000000000 mass : 5.97237e+24 satellite : Moon 

Using enumerations


The library expects enumerations in json to be presented as strings. Therefore, to use enumerations, you need to set conversion methods from string to enumeration value and vice versa, using:


MemberString::set(From function_from_string_, To function_to_string_); 

  • CDMY5CDMY - conversion function from string to enumeration value
  • CDMY6CDMY - conversion function from enum value to string

for example


enum class Color { red, blue, green, }; struct_mapping::MemberString<Color>::set( [] (const std::string & value) { if (value == "red") return Color::red; if (value == "green") return Color::green; if (value == "blue") return Color::blue; throw struct_mapping::StructMappingException("bad convert '"+value+"' to Color"); }, [] (Color value) { switch (value) { case Color::red: return "red"; case Color::green: return "green"; default: return "blue"; } }); 

Otherwise, using enumerations is similar to other types


#include <iostream> #include <list> #include <map> #include <sstream> #include <string> #include "struct_mapping/struct_mapping.h" namespace sm=struct_mapping; enum class Color { red, blue, green, }; Color color_from_string(const std::string & value) { if (value == "red") return Color::red; if (value == "blue") return Color::blue; return Color::green; } std::string color_to_string(Color color) { switch (color) { case Color::red: return "red"; case Color::green: return "green"; default: return "blue"; } } struct Palette { Color main_color; Color background_color; std::list<Color> special_colors; std::map<std::string, Color> colors; friend std::ostream & operator<<(std::ostream & os, const Palette & o) { os << "main_color : " << color_to_string(o.main_color) << std::endl; os << "background_color : " << color_to_string(o.background_color) << std::endl; os << "special_colors : "; for (auto color : o.special_colors) os << color_to_string(color) << ", "; os << std::endl << "colors : "; for (auto [name, color] : o.colors) os << "[" << name << ", " << color_to_string(color) << "], "; os << std::endl; return os; } }; int main() { sm::MemberString<Color>::set(color_from_string, color_to_string); sm::reg(&Palette::main_color, "main_color", sm::Required{}); sm::reg(&Palette::background_color, "background_color", sm::Default{Color::blue}); sm::reg(&Palette::special_colors, "special_colors"); sm::reg(&Palette::colors, "colors"); Palette palette; std::istringstream json_data(R"json( { "main_color": "green", "special_colors": ["green", "green", "red"], "colors": { "dark": "green", "light": "red", "neutral": "blue" } } )json"); sm::map_json_to_struct(palette, json_data); std::cout << palette << std::endl; } 

result


main_color : green background_color : blue special_colors : green, green, red, colors : [dark, green], [light, red], [neutral, blue], 

Display options


Now you can set options for registering a field when registering a field


  • Bounds
  • Default
  • NotEmpty
  • Required

Bounds


Sets the range of values ​​in which (including the range limits) the value to be set should be. Applicable for integer types and floating point types. The option accepts two parameters - the range limits. Throws an exception when the value set during the display is exceeded.


Bounds{нижняя граница, верхняя граница} 

Example of setting an option:


reg(&Stage::engine_count, "engine_count", Bounds{1, 31}); 

Default


Sets the default value for the field. Applicable for bool, integer types, floating point types, strings, containers, C++ structures and enumerations. The option accepts one parameter - the default value.


Default{значение по умолчанию} 

Example of setting an option:


reg(&Stage::engine_count, "engine_count", Default{3}); 

NotEmpty


Indicates that the field cannot be set to null. Applicable for strings and containers. The option does not accept parameters. Throws an exception if, after the display is complete, the field value is an empty string or an empty container.


Example of setting an option:


reg(&Spacecraft::name, "name", NotEmpty{})); 

Required


Indicates that the field must be set to a value. Applicable for bool, integer types, floating point types, strings, containers, C++ structures and enumerations. The option does not accept parameters. Throws an exception if after completing the display the value for the field has not been set.


Example of setting an option:


reg(&Spacecraft::name, "name", Required{})); 

Example of using options


#include <iostream> #include <list> #include <map> #include <sstream> #include <string> #include "struct_mapping/struct_mapping.h" namespace sm=struct_mapping; struct Stage { unsigned short engine_count; std::string fuel; long length; friend std::ostream & operator<<(std::ostream & os, const Stage & o) { os << " engine_count : " << o.engine_count << std::endl; os << " fuel : " << o.fuel << std::endl; os << " length : " << o.length << std::endl; return os; } }; struct Spacecraft { bool in_development; std::string name; int mass; std::map<std::string, Stage> stages; std::list<std::string> crew; friend std::ostream & operator<<(std::ostream & os, const Spacecraft & o) { os << "in_development : " << std::boolalpha << o.in_development << std::endl; os << "name : " << o.name << std::endl; os << "mass : " << o.mass << std::endl; os << "stages: " << std::endl; for (auto& s : o.stages) os << " " << s.first << std::endl << s.second; os << "crew: " << std::endl; for (auto& p : o.crew) os << " " << p << std::endl; return os; } }; int main() { sm::reg(&Stage::engine_count, "engine_count", sm::Default{6}, sm::Bounds{1, 31}); sm::reg(&Stage::fuel, "fuel", sm::Default{"subcooled"}); sm::reg(&Stage::length, "length", sm::Default{50}); sm::reg(&Spacecraft::in_development, "in_development", sm::Required{}); sm::reg(&Spacecraft::name, "name", sm::NotEmpty{}); sm::reg(&Spacecraft::mass, "mass", sm::Default{5000000}, sm::Bounds{100000, 10000000}); sm::reg(&Spacecraft::stages, "stages", sm::NotEmpty{}); sm::reg(&Spacecraft::crew, "crew", sm::Default{std::list<std::string>{"Arthur", "Ford", "Marvin"}}); Spacecraft starship; std::istringstream json_data(R"json( { "in_development": false, "name": "Vostok", "stages": { "first": { "engine_count": 31, "fuel": "compressed gas", "length": 70 }, "second": {} } } )json"); sm::map_json_to_struct(starship, json_data); std::cout << starship << std::endl; } 

result


in_development : false name : Vostok mass : 5000000 stages: first engine_count : 31 fuel : compressed gas length : 70 second engine_count : 6 fuel : subcooled length : 50 crew: Arthur Ford Marvin 

Reverse mapping of c++ structure to json


To reverse map a structure to json, you must first register all the fields of all structures that you want to display using for each field


reg(V T::* ptr, std::string const & name, Options<U>&&... options); 

and call the function directly to reverse display


map_struct_to_json(T & source_struct, std::basic_ostream<char> & json_data, std::string indent); 

  • CDMY7CDMY - link to the source structure
  • CDMY8CDMY - link to json data output stream
  • CDMY9CDMY - indent (if set, makes the output format better readable)

#include <iostream> #include <sstream> #include "struct_mapping/struct_mapping.h" struct OceanPart { std::string name; double average_depth; std::vector<int> temperature; }; struct OceanColor { std::string name; }; struct Ocean { double water_volume; long long surface_area; bool liquid; std::string name; OceanColor color; std::vector<OceanPart> parts; }; struct Planet { bool giant; long long surface_area; double mass; double volume; long long orbital_period; std::string name; bool terrestrial; std::string shape; Ocean ocean; }; int main() { struct_mapping::reg(&OceanPart::name, "name"); struct_mapping::reg(&OceanPart::average_depth, "average_depth"); struct_mapping::reg(&OceanPart::temperature, "temperature"); struct_mapping::reg(&OceanColor::name, "name"); struct_mapping::reg(&Ocean::water_volume, "water_volume"); struct_mapping::reg(&Ocean::surface_area, "surface_area"); struct_mapping::reg(&Ocean::liquid, "liquid"); struct_mapping::reg(&Ocean::name, "name"); struct_mapping::reg(&Ocean::color, "color"); struct_mapping::reg(&Ocean::parts, "parts"); struct_mapping::reg(&Planet::giant, "giant"); struct_mapping::reg(&Planet::surface_area, "surface_area"); struct_mapping::reg(&Planet::mass, "mass"); struct_mapping::reg(&Planet::volume, "volume"); struct_mapping::reg(&Planet::orbital_period, "orbital_period"); struct_mapping::reg(&Planet::name, "name"); struct_mapping::reg(&Planet::terrestrial, "terrestrial"); struct_mapping::reg(&Planet::shape, "shape"); struct_mapping::reg(&Planet::ocean, "ocean"); Planet earth; earth.giant=false; earth.terrestrial=true; earth.surface_area=510072000; earth.orbital_period=365 * 24 * 3600; earth.mass=5.97237e24; earth.name="Terra"; earth.volume=1.08321e12; earth.shape="nearly spherical"; earth.ocean.water_volume=1332000000; earth.ocean.surface_area=361132000; earth.ocean.liquid=true; earth.ocean.name="World Ocean"; earth.ocean.color.name="blue"; OceanPart pacific; pacific.name="Pacific Ocean"; pacific.average_depth=4.280111; pacific.temperature=std::vector<int>{-3, 5, 12}; OceanPart atlantic; atlantic.name="Atlantic Ocean"; atlantic.average_depth=3.646; atlantic.temperature=std::vector<int>{-3, 0}; earth.ocean.parts.push_back(pacific); earth.ocean.parts.push_back(atlantic); std::ostringstream json_data; struct_mapping::map_struct_to_json(earth, json_data, " "); std::cout << json_data.str() << std::endl; } 

result


{ "giant": false, "surface_area": 510072000, "mass": 5.97237e+24, "volume": 1.08321e+12, "orbital_period": 31536000, "name": "Terra", "terrestrial": true, "shape": "nearly spherical", "ocean": { "water_volume": 1.332e+09, "surface_area": 361132000, "liquid": true, "name": "World Ocean", "color": { "name": "blue" }, "parts": [ { "name": "Pacific Ocean", "average_depth": 4.28011, "temperature": [ -3, 5, 12 ] }, { "name": "Atlantic Ocean", "average_depth": 3.646, "temperature": [ -3, 0 ] } ] } } 

As a result


  • using regular C++ structures is possible (and necessary)
  • Json objects can be displayed both on a specially defined structure (this possibility remains) and on ordinary structures
  • Json arrays can be mapped to std :: vector and std :: list. The general requirements for containers on which arrays can be mapped are not yet fully formed.
  • json objects can be mapped to associative containers, with the restriction that the key must be a string. The general requirements for containers, as well as with arrays, are not yet fully formed.
  • macros are not needed and certainly not necessary. The possibility of their use remained (as an option) during registration, combined with the initialization of the fields. But most likely it will be cut.
  • the display can be customized using the options

The library is available on GitHub

.

Source