-
-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Functional style parsing for custom data members #267
Comments
I had a quick look at json_traits_macros.hpp, and I think your proposal is doable, since based on the count of items inside the parentheses, we can have different macro expansions. Since we already have 27! convenience macros, I would prefer to allow additional parameters inside parentheses in the existing I'll need to investigate further. |
For me the Another interesting property of this feature would be to validate 'inline' or with custom functions/logics. E.g. even if my field is a class Person {
public:
std::string getName();
std::string getSocialSecurityNumber();
private:
std::string _name;
std::string _socialSecurityNumber;
}
JSONCONS_ALL_FUNCTION_PARSER_TRAITS(Person,
(getName, "name"),
(getSocialSecurityNumber,
[] (const std::string& iNonValidated) {
std::regex myRegex(("[a-z]{1}[0-9]{10}"));
if (not std::regex_match(iNonValidated, myRegex) ) {
throw MyCustomException();
}
return iNonValidated;
},
std::identity,
"_socialSecurityNumber"
)
) So in this case you don't even need to 'change' the type but you provide a local fast validation method. Or maybe even more expressive: class Person {
public:
std::string getName();
std::optional<std::string> getSocialSecurityNumber();
private:
std::string _name;
std::optional<std::string> _socialSecurityNumber;
}
JSONCONS_ALL_FUNCTION_PARSER_TRAITS(Person,
(getName, "name"),
(getSocialSecurityNumber,
[] (const std::string& iNonValidated) {
std::regex myRegex(("[a-z]{1}[0-9]{10}"));
if (not std::regex_match(iNonValidated, myRegex) ) {
return {};
}
return {iNonValidated};
},
std::identity,
"_socialSecurityNumber"
)
) In this last example you can realize that if you want you could have unrelated
This could get even more interesting with the use of Rodrigo |
The JSONCONS_N_MEMBER_NAME_TRAITS(class_name,num_mandatory,
(member_name0,serialized_name0[,mode,match,from,into]),
(member_name1,serialized_name1[,mode,match,from,into])...) (5)
JSONCONS_ALL_MEMBER_NAME_TRAITS(class_name,
(member_name0,serialized_name0[,mode,match,from,into]),
(member_name1,serialized_name1[,mode,match,from,into])...) (6)
JSONCONS_TPL_N_MEMBER_NAME_TRAITS(num_template_params,
class_name,num_mandatory,
(member_name0,serialized_name0[,mode,match,from,into]),
(member_name1,serialized_name1[,mode,match,from,into])...) (7)
JSONCONS_TPL_ALL_MEMBER_NAME_TRAITS(num_template_params,
class_name,
(member_name0,serialized_name0[,mode,match,from,into]),
(member_name1,serialized_name1[,mode,match,from,into])...) (8)
JSONCONS_N_CTOR_GETTER_NAME_TRAITS(class_name,num_mandatory,
(getter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,serialized_name1[,mode,match,from,into])...) (15)
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(class_name,
(getter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,serialized_name1[,mode,match,from,into])...) (16)
JSONCONS_TPL_N_CTOR_GETTER_NAME_TRAITS(num_template_params,
class_name,num_mandatory,
(getter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,serialized_name1[,mode,match,from,into])...) (17)
JSONCONS_TPL_ALL_CTOR_GETTER_NAME_TRAITS(num_template_params,
class_name,
(getter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,serialized_name1[,mode,match,from,into])...) (18)
JSONCONS_N_GETTER_SETTER_NAME_TRAITS(class_name,num_mandatory,
(getter_name0,setter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,setter_name1,serialized_name1[,mode,match,from,into])...) (23)
JSONCONS_ALL_GETTER_SETTER_NAME_TRAITS(class_name,
(getter_name0,setter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,setter_name1,serialized_name1[,mode,match,from,into])...) (24)
JSONCONS_TPL_N_GETTER_SETTER_NAME_TRAITS(num_template_params,
class_name,num_mandatory,
(getter_name0,setter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,setter_name1,serialized_name1[,mode,match,from,into])...) (25)
JSONCONS_TPL_ALL_GETTER_SETTER_NAME_TRAITS(num_template_params,
class_name,
(getter_name0,setter_name0,serialized_name0[,mode,match,from,into]),
(getter_name1,setter_name1,serialized_name1[,mode,match,from,into])...) (26) Test cases may be found here. Code is in 0.157.0. Feedback appreciated. |
I've moved the optional function objects to the end, which I think makes the documentation clearer, and edited my post above accordingly. |
Will try to play around with it very soon :) |
I've modified the macros so you can omit the second function object in your validation example. |
The first validation example ( #include <jsoncons/json.hpp>
#include <assert>
class Person
{
std::string name_;
std::optional<std::string> socialSecurityNumber_;
public:
Person(const std::string& name, const std::optional<std::string>& socialSecurityNumber)
: name_(name), socialSecurityNumber_(socialSecurityNumber)
{
}
std::string getName() const
{
return name_;
}
std::optional<std::string> getSsn() const
{
return socialSecurityNumber_;
}
};
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(Person,
(getName, "name"),
(getSsn, "social_security_number",
JSONCONS_RDWR,
[] (const jsoncooptional<std::string>& unvalidated) {
if (!unvalidated)
{
return false;
}
std::regex myRegex("^(\\d{9})$");
return std::regex_match(*unvalidated, myRegex);
}
)
)
using namespace jsoncons;
int main()
{
std::vector<Person> persons1 = {Person("John Smith", "123456789"), Person("Jane Doe", "12345678")};
std::string output1;
encode_json_pretty(persons1, output1);
try
{
auto persons2 = decode_json<std::vector<Person>>(output1);
}
catch (const std::exception& e)
{
std::cout << e.what() << "\n\n";
}
} Output:
The second validation example ( #include <jsoncons/json.hpp>
#include <assert>
class Person
{
std::string name_;
jsoncooptional<std::string> socialSecurityNumber_;
public:
Person(const std::string& name, const jsoncooptional<std::string>& socialSecurityNumber)
: name_(name), socialSecurityNumber_(socialSecurityNumber)
{
}
std::string getName() const
{
return name_;
}
jsoncooptional<std::string> getSsn() const
{
return socialSecurityNumber_;
}
};
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(Person,
(getName, "name"),
(getSsn, "social_security_number",
JSONCONS_RDWR, jsoncoalways_true(),
[] (const jsoncons::optional<std::string>& unvalidated) {
if (!unvalidated)
{
return unvalidated;
}
std::regex myRegex(("^(\\d{9})$"));
if (!std::regex_match(*unvalidated, myRegex) ) {
return jsoncooptional<std::string>();
}
return unvalidated;
}
)
)
using namespace jsoncons;
int main()
{
std::string input = R"(
[
{
"name": "John Smith",
"social_security_number": "123456789"
},
{
"name": "Jane Doe",
"social_security_number": "12345678"
}
]
)";
auto persons = decode_json<std::vector<Person>>(input);
std::cout << "(1)\n";
for (const auto& person : persons)
{
std::cout << person.getName() << ", "
<< (person.getSsn() ? *person.getSsn() : "n/a") << "\n";
}
std::cout << "\n";
std::string output;
encode_json_pretty(persons, output);
std::cout << "(2)\n" << output << "\n";
}
} Output:
|
Dear Daniel, thank you very much for your help. I'm sorry I didn't have time during the week to play with it. I still plan to heavily use this feature but it might be deferred a bit. |
I've added optional #include <jsoncons/json.hpp>
namespace ns {
class Shape
{
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Rectangle : public Shape
{
double height_;
double width_;
public:
Rectangle(double height, double width)
: height_(height), width_(width)
{
}
double height() const
{
return height_;
}
double width() const
{
return width_;
}
double area() const override
{
return height_ * width_;
}
const std::string type() const
{
return "rectangle";
}
};
class Triangle : public Shape
{
double height_;
double width_;
public:
Triangle(double height, double width)
: height_(height), width_(width)
{
}
double height() const
{
return height_;
}
double width() const
{
return width_;
}
double area() const override
{
return (height_ * width_)/2.0;
}
std::string type() const
{
return "triangle";
}
};
class Circle : public Shape
{
double radius_;
public:
Circle(double radius)
: radius_(radius)
{
}
double radius() const
{
return radius_;
}
double area() const override
{
constexpr double pi = 3.14159265358979323846;
return pi*radius_*radius_;
}
std::string type() const
{
return "circle";
}
};
} // namespace ns
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Rectangle,
(type,"type",JSONCONS_RDONLY,[](const std::string& type){return type == "rectangle";}),
(height, "height", JSONCONS_RDWR),
(width, "width")
)
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Triangle,
(type,"type", JSONCONS_RDONLY, [](const std::string& type){return type == "triangle";}),
(height, "height"),
(width, "width")
)
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Circle,
(type,"type", JSONCONS_RDONLY, [](const std::string& type){return type == "circle";}),
(radius, "radius")
)
JSONCONS_POLYMORPHIC_TRAITS(ns::Shape,ns::Rectangle,ns::Triangle,ns::Circle)
using namespace jsoncons;
int main()
{
std::string input = R"(
[
{"type" : "rectangle", "width" : 2.0, "height" : 1.5 },
{"type" : "triangle", "width" : 3.0, "height" : 2.0 },
{"type" : "circle", "radius" : 1.0 }
]
)";
auto shapes = decode_json<std::vector<std::unique_ptr<ns::Shape>>>(input);
REQUIRE(shapes.size() == 3);
for (const auto& shape : shapes)
{
std::cout << "area: " << shape->area() << "\n";
}
std::string output;
encode_json_pretty(shapes, output);
std::cout << "\n" << output << "\n";
} Output:
|
Nice feature ! |
See also #277 |
Using the MACRO style would be nice if we could provide a custom lambda/method/fucntion pointer defining the serialization/deserialization for a class member on a class body. A good example of usage would be for serialization and deserialization of DateTime formats.
For example let's suppose I want the following type to be JSON serializable/deserializable:
Let's suppose that the custom
DateTime
structure can be easily converted/represented by astd::string
it would be very convenient to use the jsoncons macros to bind converters from and to string. E.g.:Let's assume I have those two methods defined somewhere:
I would like to be able to use one of the MACROS like the following snippet :
More generally would be nice if the parameters could accept also lambdas:
It seems ambitious and I don't know the perks of the implementation but as user it would greatly benefit us.
More generally the functions / lambdas to be provided are constrained in a way that:
As another example let's say I want to deserialize the following json:
into the following data structure:
and let's say that JSONCONS knows how to deserialize/deserialize the following data structure (proper MACRO provided in order to tell jsoncons how to do it):
Let's say that I have two internal functions that are able to map between
std::vector<Employee>
andstd::vector<uint64_t>
like:I would like to provide the following macro to deserialize from the initial json:
Another huge value for that is that it would provide the ability for users to include Data types of third part libraries into the class members, the user only need to 'translate' from/to json in a functional way... (hope it's clear :) )
So even if my code was not the one to define
DateTime
datastructure I could define the feasible conversion from and to it.(If there is already something similar I would be very interested to know)
The text was updated successfully, but these errors were encountered: