c++boostxml-parsingxml-serializationboost-optional

How to Handle Optional Xml Elements using C++ boost::serialization


I'm using C++ boost::serialization library to read and write configuration XMLs. To provide backward compatibility to the user, while reading an XML if some XML elements are absent, it needs to have default values. It will throw input stream error if the xml element that it is trying read is missing.

I tried using Try-Catch blocks to solve this, but this way also i'm getting the input stream error.

void XmlAttributes::serialize(Archive & ar, const unsigned int file_version ){
    ar  & boost::serialization::make_nvp("Attr1",attr1);    

    try {
        ar  & boost::serialization::make_nvp("Attr2", attr2);
    }
    catch (boost::archive::archive_exception const& e) {
        // XML element not found, set default value
        attr2 = true; // Set a default value, for example, true
    }
}

In the above code "Attr1" is mandatory one in the XML, but "Attr2" is optional element, so it should default to 'true' if it doesn't exist in the XML without any input stream errors. Is there any way to use boost optional libraries to solve this issue.


Solution

  • Boost Serialization does not deal with XML attributes. It deals with XML archives.

    You control serialization by defining serialization for your type.

    If you need backwards compatibility you need to version that definition. Luckily, Boost Serialization allows you to do just that: https://www.boost.org/doc/libs/1_84_0/libs/serialization/doc/

    Demo

    Live On Coliru

    #include <boost/archive/xml_iarchive.hpp>
    #include <boost/archive/xml_oarchive.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <fstream>
    #include <iostream>
    #include <set>
    using boost::serialization::make_nvp;
    
    struct XmlAttributes {
        XmlAttributes(std::string attr1 = {}, bool attr2 = false) //
            : attr1(std::move(attr1))
            , attr2(attr2) {}
    
      private:
        std::string attr1;
        bool        attr2;
        friend class boost::serialization::access;
    
        template <typename Archive> void serialize(Archive& ar, unsigned version) {
            if (Archive::is_loading::value)
                std::cout << "Loading version: " << version << std::endl;
            else
                std::cout << "Saving version: " << version << std::endl;
    
            ar& make_nvp("Attr1", attr1);
            if (version >= 1)
                ar& make_nvp("Attr2", attr2);
            else
                attr2 = true; // Set a default value, for example, true
        }
    
        friend std::ostream& operator<<(std::ostream& os, XmlAttributes const& attr) {
            return os << "Attr1: " << attr.attr1 << ", Attr2: " << attr.attr2;
        }
    };
    
    #ifdef NEWVERSION
        BOOST_CLASS_VERSION(XmlAttributes, 1)
    #endif
    
    int main(int argc, char** argv) {
        std::cout << "\n---\nProgram Version: " << boost::serialization::version<XmlAttributes>::value << std::endl;
    
        std::set<std::string_view> args(argv, argv + argc);
        if (args.contains("load")) {
            XmlAttributes attr;
    
            std::ifstream ifs("test.xml");
            boost::archive::xml_iarchive(ifs) >> make_nvp("test", attr);
    
            std::cout << attr << std::endl;
        }
    
        if (args.contains("save")) {
            XmlAttributes attr
            {"Hello", false};
    
            std::ofstream ofs("test.xml");
            boost::archive::xml_oarchive(ofs) << make_nvp("test", attr);
        }
    }
    

    Testing by saving with old version, loading and saving it again with the new version:

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_serialization -o oldversion
    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lboost_serialization -o newversion -DNEWVERSION
    ./oldversion save
    nl test.xml
    ./newversion load save 
    nl test.xml
    ./oldversion load
    

    Shows

    ---
    Program Version: 0
    Saving version: 0
         1  <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
         2  <!DOCTYPE boost_serialization>
         3  <boost_serialization signature="serialization::archive" version="20">
         4  <test class_id="0" tracking_level="0" version="0">
         5      <Attr1>Hello</Attr1>
         6  </test>
         7  </boost_serialization>
           
    ---
    Program Version: 1
    Loading version: 0
    Attr1: Hello, Attr2: 1
    Saving version: 1
         1  <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
         2  <!DOCTYPE boost_serialization>
         3  <boost_serialization signature="serialization::archive" version="20">
         4  <test class_id="0" tracking_level="0" version="1">
         5      <Attr1>Hello</Attr1>
         6      <Attr2>0</Attr2>
         7  </test>
         8  </boost_serialization>
           
    ---
    Program Version: 0
    terminate called after throwing an instance of 'boost::archive::archive_exception'
      what():  class version 13XmlAttributes
    

    Interactive demonstration for clarity:

    enter image description here