c++boostboost-jsontag-invoke

Boost Json conversion error for array of custom object


I'm trying to code a simple json to struct (and back) conversion by using tag_invoke overload of boost::json lib.

Those are my structs:

template<class T>
void extract( boost::json::object const& obj, T& t, boost::json::string_view key )
{
    t = boost::json::value_to<T>( obj.at( key ) );
};

struct CRDApp {
    std::string type;
    std::string image;
    uint32_t replicas;

    friend CRDApp tag_invoke( boost::json::value_to_tag<CRDApp>, boost::json::value const& jv )
    {
        CRDApp app;
        boost::json::object const& obj = jv.as_object();
        extract( obj, app.type, "type" );
        extract( obj, app.image, "image" );
        extract( obj, app.replicas, "replicas" );
        return app;
    }

    friend void tag_invoke( boost::json::value_from_tag, boost::json::value& jv, CRDApp const& app )
    {
        jv = {
            { "type" , app.type },
            { "image", app.image },
            { "replicas", app.replicas }
        };
    }
};

struct CRDSpec {
    std::string _namespace;
    std::vector<CRDApp> apps;

    friend CRDSpec tag_invoke( boost::json::value_to_tag<CRDSpec>, boost::json::value const& jv )
    {
        CRDSpec spec;
        boost::json::object const& obj = jv.as_object();
        extract( obj, spec._namespace, "namespace" );
        extract( obj, spec.apps, "apps" );
        return spec;
    }

    friend void tag_invoke( boost::json::value_from_tag, boost::json::value& jv, CRDSpec const& spec )
    {   
        jv = {
            { "namespace" , spec._namespace },
            { "apps", spec.apps }
        };
    }
};

I've tested the json to struct conversion and is working fine, but once I've added the tag_invoke in order to convert from struct to json, the code is not compiling anymore with error:

error: no matching function for call to 'boost::json::value::value(const std::vector<CRDApp>&, std::remove_reference<boost::json::storage_ptr&>::type)'
   35 |     return value(
      |            ^~~~~~
   36 |         *reinterpret_cast<
      |         ~~~~~~~~~~~~~~~~~~
   37 |             T const*>(p),
      |             ~~~~~~~~~~~~~
   38 |         std::move(sp));
      |         ~~~~~~~~~~~~~~

If I comment out the { "apps", spec.apps } line, the code compile again. Docs say it should automatically handle standard containers like std::vector.

Am I missing something?


Solution

  • The whole idea of the tag_invoke customization is not that you get "magic" or implicit conversions. You have to call it:

    jv = {{"namespace", spec._namespace}, {"apps", boost::json::value_from(spec.apps)}};
    

    Live On Coliru

    #include <boost/json/src.hpp>
    #include <iostream>
    
    template <class T> void extract(boost::json::object const& obj, T& v, boost::json::string_view key) {
        v = boost::json::value_to<T>(obj.at(key));
    };
    
    struct CRDApp {
        std::string type;
        std::string image;
        uint32_t    replicas;
    
        friend CRDApp tag_invoke(boost::json::value_to_tag<CRDApp>, boost::json::value const& jv) {
            CRDApp                     app;
            boost::json::object const& obj = jv.as_object();
            extract(obj, app.type, "type");
            extract(obj, app.image, "image");
            extract(obj, app.replicas, "replicas");
            return app;
        }
    
        friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, CRDApp const& app) {
            jv = {{"type", app.type}, {"image", app.image}, {"replicas", app.replicas}};
        }
    
        auto operator<=>(CRDApp const&) const = default;
    };
    
    struct CRDSpec {
        std::string         _namespace;
        std::vector<CRDApp> apps;
    
        friend CRDSpec tag_invoke(boost::json::value_to_tag<CRDSpec>, boost::json::value const& jv) {
            CRDSpec                    spec;
            boost::json::object const& obj = jv.as_object();
            extract(obj, spec._namespace, "namespace");
            extract(obj, spec.apps, "apps");
            return spec;
        }
    
        friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, CRDSpec const& spec) {
            jv = {{"namespace", spec._namespace}, {"apps", boost::json::value_from(spec.apps)}};
        }
    
        auto operator<=>(CRDSpec const&) const = default;
    };
    
    int main() {
        CRDSpec const spec{"some_ns",
                           {
                               {"type1", "image1", 111},
                               {"type2", "image2", 222},
                               {"type3", "image3", 333},
                               {"type4", "image4", 444},
                               {"type5", "image5", 555},
                               {"type6", "image6", 666},
                               {"type7", "image7", 777},
                               {"type8", "image8", 888},
                               {"type9", "image9", 999},
                           }};
    
        auto js = boost::json::value_from(spec);
        std::cout << js << "\n";
    
        auto roundtrip = boost::json::value_to<CRDSpec>(js);
        std::cout << "Roundtrip " << (roundtrip == spec? "equal":"different") << "\n";
    }
    

    Prints

    {"namespace":"some_ns","apps":[{"type":"type1","image":"image1","replicas":111},{"type":"type2","image":"image2","replicas":222},{"type":"type3","image":"image3","replicas":333},{"type":"type4","image":"image4","replicas":444},{"type":"type5","image":"image5","replicas":555},{"type":"type6","image":"image6","replicas":666},{"type":"type7","image":"image7","replicas":777},{"type":"type8","image":"image8","replicas":888},{"type":"type9","image":"image9","replicas":999}]}
    Roundtrip equal