Search code examples
c++xmlboostxml-serialization

How to change or delete tags in boost serialization?


I'm trying to serialize my classes to xml. My classes;

class HardwareDto{
    friend class boost::serialization::access;
    template<class Archive> void serialize(Archive & ar, const unsigned int version) {
        ar & BOOST_SERIALIZATION_NVP(HardwareID);
        ar & BOOST_SERIALIZATION_NVP(HardwareHostID);
        ar & BOOST_SERIALIZATION_NVP(HardwareFriendlyName);
    }
public:
    int HardwareID;
    int HardwareHostID;
    string HardwareFriendlyName;
    inline HardwareDto(int HardwareHostID, int HardwareID, string HardwareFriendlyName) {
        this->HardwareHostID = HardwareHostID;
        this->HardwareID = HardwareID;
        this->HardwareFriendlyName = HardwareFriendlyName;
    }
};

And a class which contains a HardwareDto list.

class HardwareHostDto {
private:
    friend class boost::serialization::access;
    template<class Archive> void serialize(Archive & ar, const unsigned int version) {
        ar & BOOST_SERIALIZATION_NVP(HardwareHostID);
        ar & BOOST_SERIALIZATION_NVP(BranchID);
        ar & BOOST_SERIALIZATION_NVP(HardwareHostFriendlyName);
        ar & BOOST_SERIALIZATION_NVP(HardwareList);
    }

public:
    int HardwareHostID;
    int BranchID;
    string HardwareHostFriendlyName ;
    HardwareDto* HardwareList[20];

    inline HardwareHostDto(int HardwareHostID, int BranchID, string HardwareHostFriendlyName, HardwareDto* HardwareList[20]) {
        this->HardwareHostID = HardwareHostID;
        this->BranchID = BranchID;
        this->HardwareHostFriendlyName = HardwareHostFriendlyName;
        this->HardwareList[0] = HardwareList[0];
    }
};

And

HardwareDto *HardwareList[20]; 

is my global hardwaredto list. In this example I only inserted one hardwarehostdto object into this list.

I'm trying to serialize this via boost function:

std::ofstream ofs("filename.xml");

unsigned int flags = boost::archive::no_header;
boost::archive::xml_iarchive ia(is, boost::archive::no_header);
boost::archive::xml_oarchive oa(ofs, flags);

HardwareHostDto* HardwareHost = new HardwareHostDto(1, 1, "kiosk", HardwareList);

oa << BOOST_SERIALIZATION_NVP(HardwareHost);

After this code executed, i got this filename.xml:

<HardwareHost class_id="0">
    <HardwareHostID>1</HardwareHostID>
    <BranchID>1</BranchID>
    <HardwareHostFriendlyName>kiosk</HardwareHostFriendlyName>
    <HardwareList>
        <count>20</count>
        <item class_id="1">
            <HardwareID>2</HardwareID>
            <HardwareHostID>2</HardwareHostID>
            <HardwareFriendlyName>Ankara</HardwareFriendlyName>
        </item>
    </HardwareList>
</HardwareHost>

<item> tag should be <Hardware> but i cant change it. My question is: is there any way to change <item> tag, or actullay customize this xml structure, like no <count> tag or flags? I found a few ways to do it in boost website but couldnt handle it.

Thank you.


Solution

  • Yes, you can hack it. Maybe. To an extent. See:

    And no, you shouldn't. Use an XML library to write arbitrary XML.

    Boost Serialization only does serialization. The archive format is implementation detail.


    Bad Example

    What not to do (it breaks non-default constructible types, it breaks versioning).

    On the bright side, this code doesn't leak memory.

    Live On Coliru

    #include <boost/archive/xml_iarchive.hpp>
    #include <boost/archive/xml_oarchive.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/vector.hpp>
    
    class HardwareDto {
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive &ar, unsigned) {
            ar &BOOST_SERIALIZATION_NVP(HardwareID);
            ar &BOOST_SERIALIZATION_NVP(HardwareHostID);
            ar &BOOST_SERIALIZATION_NVP(HardwareFriendlyName);
        }
    
      public:
        int HardwareHostID;
        int HardwareID;
        std::string HardwareFriendlyName;
    
        HardwareDto(int HardwareHostID = -1, int HardwareID = -1, std::string HardwareFriendlyName = {})
           : HardwareHostID(HardwareHostID),
             HardwareID(HardwareID),
             HardwareFriendlyName(HardwareFriendlyName)
        { }
    };
    
    using HardwareDtoList = std::vector<HardwareDto>;
    
    class HardwareHostDto {
      private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive &ar, unsigned) {
            ar &BOOST_SERIALIZATION_NVP(HardwareHostID);
            ar &BOOST_SERIALIZATION_NVP(BranchID);
            ar &BOOST_SERIALIZATION_NVP(HardwareHostFriendlyName);
            ar &BOOST_SERIALIZATION_NVP(HardwareList);
        }
    
      public:
        int HardwareHostID;
        int BranchID;
        std::string HardwareHostFriendlyName;
        HardwareDtoList HardwareList;
    
        HardwareHostDto(int HardwareHostID, int BranchID, std::string HardwareHostFriendlyName, HardwareDtoList HardwareList) 
            : HardwareHostID(HardwareHostID),
              BranchID(BranchID),
              HardwareHostFriendlyName(HardwareHostFriendlyName),
              HardwareList(HardwareList)
        { }
    };
    
    namespace boost { namespace serialization {
        template <typename Ar>
            void serialize(Ar& ar, std::vector<HardwareDto>& v, unsigned) {
                size_t count = v.size();
                ar & BOOST_SERIALIZATION_NVP(count);
                v.resize(count);
    
                for (auto& el : v)
                    ar & boost::serialization::make_nvp("Hardware", el);
            }
    } }
    
    #include <fstream>
    #include <iostream>
    
    int main() {
        unsigned int flags = boost::archive::no_header;
    
        {
            HardwareDtoList HardwareList;
            HardwareList.emplace_back(1, 2, "friendly");
    
            std::ofstream ofs("filename.xml");
            boost::archive::xml_oarchive oa(ofs, flags);
    
            HardwareHostDto host(1, 1, "kiosk", HardwareList);
    
            oa << boost::serialization::make_nvp("HardwareHost", host);
        }
    
        {
            HardwareHostDto roundtrip(-1, -1, "", {});
            std::ifstream ifs("filename.xml");
            boost::archive::xml_iarchive ia(ifs, flags);
    
            ia >> boost::serialization::make_nvp("HardwareHost", roundtrip);
    
            std::cout << "Read back: " << roundtrip.HardwareHostID << "\n";
            std::cout << "Read back: " << roundtrip.BranchID << "\n";
            std::cout << "Read back: " << roundtrip.HardwareHostFriendlyName << "\n";
            for (auto& h: roundtrip.HardwareList) {
                std::cout << "Item: " << h.HardwareID << ", " << h.HardwareHostID << ", " << h.HardwareFriendlyName << "\n";
            }
        }
    
    }
    

    Prints

    Read back: 1
    Read back: 1
    Read back: kiosk
    Item: 2, 1, friendly
    

    And writes the XML as:

    <HardwareHost class_id="0" tracking_level="0" version="0">
        <HardwareHostID>1</HardwareHostID>
        <BranchID>1</BranchID>
        <HardwareHostFriendlyName>kiosk</HardwareHostFriendlyName>
        <HardwareList class_id="1" tracking_level="0" version="0">
            <count>1</count>
            <Hardware class_id="2" tracking_level="0" version="0">
                <HardwareID>2</HardwareID>
                <HardwareHostID>1</HardwareHostID>
                <HardwareFriendlyName>friendly</HardwareFriendlyName>
            </Hardware>
        </HardwareList>
    </HardwareHost>
    

    What To Do

    Using an XML library. This calls for (a lot) generalization, but here's a start using PugiXML:

    Live On Coliru

    #include <pugixml.hpp>
    #include <iostream>
    #include <vector>
    
    struct HardwareDto {
        int HardwareHostID;
        int HardwareID;
        std::string HardwareFriendlyName;
    };
    
    struct HardwareHostDto {
        int HardwareHostID;
        int BranchID;
        std::string HardwareHostFriendlyName;
        std::vector<HardwareDto> HardwareList;
    };
    
    struct Xml {
        struct Saver {
            template <typename T>
            void operator()(pugi::xml_node parent, std::string const& name, T const& value) const {
                auto node = named_child(parent, name);
                node.text().set(to_xml(value));
            }
    
            void operator()(pugi::xml_node parent, std::string const& name, HardwareDto const& o) const {
                auto dto = named_child(parent, name);
                operator()(dto, "HardwareHostID", o.HardwareHostID);
                operator()(dto, "HardwareID", o.HardwareID);
                operator()(dto, "HardwareFriendlyName", o.HardwareFriendlyName);
            }
    
            template <typename C>
            void operator()(pugi::xml_node parent, std::string const& name, std::string const& item_name, C const& container) const {
                auto list = named_child(parent, name);
    
                for (auto& item : container)
                    operator()(list, item_name, item);
            }
    
            void operator()(pugi::xml_node parent, std::string const& name, HardwareHostDto const& o) const {
                auto dto = named_child(parent, name);
                operator()(dto, "HardwareHostID", o.HardwareHostID);
                operator()(dto, "BranchID", o.BranchID);
                operator()(dto, "HardwareHostFriendlyName", o.HardwareHostFriendlyName);
                operator()(dto, "HardwareList", "Hardware", o.HardwareList);
            }
          private:
            // serialization
            template <typename T> static T const& to_xml(T const& v) { return v; }
            static char const* to_xml(std::string const& v) { return v.c_str(); }
    
            pugi::xml_node named_child(pugi::xml_node parent, std::string const& name) const {
                auto child = parent.append_child();
                child.set_name(name.c_str());
                return child;
            }
        };
    
        struct Loader {
            void operator()(pugi::xml_node parent, std::string const& name, std::string& value) const {
                auto node = parent.first_element_by_path(name.c_str());
                value = node.text().as_string();
            }
            void operator()(pugi::xml_node parent, std::string const& name, int& value) const {
                auto node = parent.first_element_by_path(name.c_str());
                value = node.text().as_int();
            }
    
            void operator()(pugi::xml_node dto, HardwareDto& o) const {
                operator()(dto, "HardwareHostID", o.HardwareHostID);
                operator()(dto, "HardwareID", o.HardwareID);
                operator()(dto, "HardwareFriendlyName", o.HardwareFriendlyName);
            }
    
            void operator()(pugi::xml_node parent, std::string const& name, HardwareDto& o) const {
                auto dto = parent.first_element_by_path(name.c_str());
                operator()(dto, "HardwareHostID", o.HardwareHostID);
                operator()(dto, "HardwareID", o.HardwareID);
                operator()(dto, "HardwareFriendlyName", o.HardwareFriendlyName);
            }
    
            template <typename C>
            void operator()(pugi::xml_node parent, std::string const& name, std::string const& item_name, C& container) const {
                auto list = parent.first_element_by_path(name.c_str());
    
                for (auto& node : list) {
                    if (node.type() != pugi::xml_node_type::node_element) {
                        std::cerr << "Warning: unexpected child node type ignored\n";
                        continue;
                    }
                    if (node.name() != item_name) {
                        std::cerr << "Warning: unexpected child node ignored (" << node.name() << ")\n";
                        continue;
                    }
    
                    container.emplace_back();
                    operator()(node, container.back());
                }
            }
    
            void operator()(pugi::xml_node dto, HardwareHostDto& o) const {
                operator()(dto, "HardwareHostID", o.HardwareHostID);
                operator()(dto, "BranchID", o.BranchID);
                operator()(dto, "HardwareHostFriendlyName", o.HardwareHostFriendlyName);
                operator()(dto, "HardwareList", "Hardware", o.HardwareList);
            }
    
            void operator()(pugi::xml_node parent, std::string const& name, HardwareHostDto& o) const {
                operator()(parent.first_element_by_path(name.c_str()), o);
            }
        };
    };
    
    int main() {
        {
    
            pugi::xml_document _doc;
            Xml::Saver saver;
    
            HardwareHostDto host = { 1, 1, "kiosk", { { 1, 2, "friendly" } } };
            saver(_doc.root(), "HardwareHost", host);
    
            _doc.save_file("test.xml");
        }
    
        {
            HardwareHostDto roundtrip;
            {
                pugi::xml_document _doc;
                _doc.load_file("test.xml");
                Xml::Loader loader;
    
                loader(_doc.root(), "HardwareHost", roundtrip);
            }
    
            std::cout << "Read back: " << roundtrip.HardwareHostID << "\n";
            std::cout << "Read back: " << roundtrip.BranchID << "\n";
            std::cout << "Read back: " << roundtrip.HardwareHostFriendlyName << "\n";
            for (auto& h: roundtrip.HardwareList) {
                std::cout << "Item: " << h.HardwareID << ", " << h.HardwareHostID << ", " << h.HardwareFriendlyName << "\n";
            }
        }
    
    }
    

    Which also prints

    Read back: 1
    Read back: 1
    Read back: kiosk
    Item: 2, 1, friendly
    

    And writes a test.xml:

    <?xml version="1.0"?>
    <HardwareHost>
        <HardwareHostID>1</HardwareHostID>
        <BranchID>1</BranchID>
        <HardwareHostFriendlyName>kiosk</HardwareHostFriendlyName>
        <HardwareList>
            <Hardware>
                <HardwareHostID>1</HardwareHostID>
                <HardwareID>2</HardwareID>
                <HardwareFriendlyName>friendly</HardwareFriendlyName>
            </Hardware>
        </HardwareList>
    </HardwareHost>