Skip to content
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 revisited #277

Closed
danielaparker opened this issue Oct 7, 2020 · 0 comments
Closed

Functional style parsing for custom data members revisited #277

danielaparker opened this issue Oct 7, 2020 · 0 comments

Comments

@danielaparker
Copy link
Owner

danielaparker commented Oct 7, 2020

Functional style parsing for custom data members was requested in #267 and introduced in 0.157.0.

In 0.157.0, the _NAME_ convenience macros were augmented to allow an optional Mode parameter (JSONCONS_RDWR or JSONCONS_RDONLY) and three function objects, Match (value matches expected), From (convert from type known to jsoncons) and Into (convert into type known to jsoncons).

There are two issues.

First, providing a lambda expression for the Into function object will result in an error message such as "lambda-expression in unevaluated context" (at least until C++20), because the into function object is used inside a decltype specifier, and that breaks a restriction on lambda expressions. This restriction appears to have been removed in C++20. In the meantime, it needs to be noted that we cannot pass a lambda expression for this parameter. Instead we need to pass a free function, a struct object with the operator() defined, or a variable containing a lambda expression.

Second, when JSONCONS_RDONLY is provided for the Mode parameter, the From parameter is redundant, so it is more convenient to reverse the order of the From and Into parameters. This change has been made on master, and will be included in forthcoming version 0.158.0.

To illustrate, below is a version of the polymorphic shapes example that does not require a type member. More generally, the example shows how to augment the JSON output with name/value pairs that are not present in the class definition, and to perform type selection with them.

#include <iostream>
#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_;
        }
    };

    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;
        }
    };                 

    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_;
        }
    };                 

    inline constexpr auto rectangle_marker = [](double) noexcept {return "rectangle"; };
    inline constexpr auto triangle_marker = [](double) noexcept {return "triangle";};
    inline constexpr auto circle_marker = [](double) noexcept {return "circle";};

} // namespace ns

JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Rectangle,
    (height,"type",JSONCONS_RDONLY,
     [](const std::string& type) noexcept{return type == "rectangle";},
     ns::rectangle_marker),
    (height, "height"),
    (width, "width")
)

JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Triangle,
    (height,"type", JSONCONS_RDONLY, 
     [](const std::string& type) noexcept {return type == "triangle";},
     ns::triangle_marker),
    (height, "height"),
    (width, "width")
)

JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Circle,
    (radius,"type", JSONCONS_RDONLY, 
     [](const std::string& type) noexcept {return type == "circle";},
     ns::circle_marker),
    (radius, "radius")
)

JSONCONS_POLYMORPHIC_TRAITS(ns::Shape,ns::Rectangle,ns::Triangle,ns::Circle)

int main()
{
    std::string input = R"(
[
    {"type" : "rectangle", "width" : 2.0, "height" : 1.5 },
    {"type" : "triangle", "width" : 4.0, "height" : 2.0 },
    {"type" : "circle", "radius" : 1.0 }
]
    )";

    auto shapes = jsoncons::decode_json<std::vector<std::unique_ptr<ns::Shape>>>(input);

    std::cout << "(1)\n";
    for (const auto& shape : shapes)
    {
        std::cout << typeid(*shape.get()).name() << " area: " << shape->area() << "\n";
    }

    std::string output;

    jsoncons::encode_json_pretty(shapes, output);
    std::cout << "\n(2)\n" << output << "\n";
}

Output:

(1)
class ns::Rectangle area: 3.000000
class ns::Triangle area: 4.000000
class ns::Circle area: 3.141593

(2)
[
    {
        "height": 1.5,
        "type": "rectangle",
        "width": 2.0
    },
    {
        "height": 2.0,
        "type": "triangle",
        "width": 4.0
    },
    {
        "radius": 1.0,
        "type": "circle"
    }
]

The validation example (which provided a From but omitted the Into function object) now becomes

#include <jsoncons/json.hpp>

namespace ns {

   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_;
         }
   };

} // namespace ns

JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Person, 
  (getName, "name"),
  (getSsn, "social_security_number",
      jsoncons::always_true(),
      jsoncons::identity(), // or std::identity() if C++20
      [] (const std::optional<std::string>& unvalidated) {
          if (!unvalidated)
          {
              return unvalidated;
          }
          std::regex myRegex(("^(\\d{9})$"));
          if (!std::regex_match(*unvalidated, myRegex) ) {
              return std::optional<std::string>();
          }
          return unvalidated;
      }
   )
)

int main()
{
        std::string input = R"(
[
    {
        "name": "John Smith",
        "social_security_number": "123456789"
    },
    {
        "name": "Jane Doe",
        "social_security_number": "12345678"
    }
]
    )";

    auto persons = jsoncons::decode_json<std::vector<ns::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;
    jsoncons::encode_json_pretty(persons, output);
    std::cout << "(2)\n" << output << "\n";
}

Output:

(1)
John Smith, 123456789
Jane Doe, n/a

(2)
[
    {
        "name": "John Smith",
        "social_security_number": "123456789"
    },
    {
        "name": "Jane Doe",
        "social_security_number": null
    }
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant