Electively serialize to/from XML or binary in C++

Usage

Believe it or not, we now have everything we need to (de)serialize from/to binary or XML. Integration is beyond the scope of this article, but I won’t leave you without some basic usage examples. Here’s how I use the serializers in my project.
A menu event handler (in my wxWidgets based app) displays a file-save dialog and then delegates the saving to the World‘s own saveToFile method, which is shown below.

void vte::editor::MainFrame::onFileSaveAs( wxCommandEvent& event )
{
	wxFileDialog* dialog = new wxFileDialog( this, "Save as", wxGetCwd(), "", VTE_FILE_TYPES, wxSAVE );
	if( dialog->ShowModal() == wxID_OK )
	{
		std::string fileName = dialog->GetPath().c_str();
		vte::log( "Saving world to file '%s'...", fileName.c_str() );
		vte::world::EWorldFormat format;
		s32 filter = dialog->GetFilterIndex();
		if( filter == 0 )
		{
			format = vte::world::XML;
		}
		else if( filter == 1 )
		{
			format = vte::world::BINARY;
		}
		else
		{
			format = DetectFormatFromFileName( fileName );
		}
		vte::world::getWorld()->saveToFile( fileName, format );
	}
	dialog->Destroy();
}
void vte::world::World::saveToFile( const std::string& fileName, vte::world::EWorldFormat format )
{
	VTE_ASSERT( format != AUTODETECT ); // AUTODETECT only allowed for load
	if( format == XML )
	{
		vte::core::XMLSerializer xml;
		writeTo( &xml );
		xml.save( fileName );
	}
	else if( format == BINARY )
	{
		vte::core::BinarySerializationSize binarySize;
		writeTo( &binarySize );
		u32 bufferSize = binarySize.getSize();
		u8* buffer = new u8[ bufferSize ];
		vte::core::BinarySerializer binary( buffer, bufferSize );
		writeTo( &binary );
		std::ofstream out( fileName.c_str(), std::ios::binary | std::ios::in | std::ios::trunc );
		out.write( "VTE\0", 4 );
		out.write( reinterpret_cast< const c8* >( buffer ), bufferSize );
		out.close();
		delete[] buffer;
	}
}

This version of XMLSerializer has an additional convenience method save which saves the TiXmlDocument to the given file.

Limitations and Extensions

  • Remember the address book example? What if also an optional photo file is to be saved? The binary format would simply write the length of the data and the binary image data itself, XML requires some extra processing, i.e. either the data has to be embedded in <![CDATA[...]]> tags or encoded into ASCII bytes using UUencode or base64 encoding. The problem is that parameters are currently written as XML attributes, but it’s common practice to embed binary data into an extra child tag. You could simply create an “internal” child node but the code would need some rework to support this.
  • The Serializable class was not used at all in this article. So why should we have a common base class at all? Depending on the abstraction of your application framework, your resource access layer most likely won’t know anything about the nature of the objects you serialize. My usage example is pretty simple, but you could also delegate the entire file access to this resource access layer. Instead of calling an explicit saveToFile you could pass the World object, which is Serializable itself, to the resource access layer which then takes further actions. Moreover, you can use the common base class to chain up elements which are to be serialized.
  • beginAttribute and endAttribute currently do nothing in the binary implementation. One could think about creating chunks from the given names in order to identify the location of information quickly even in the binary format. Other file formats like 3DS use this technique in order to allow the user to skip uninteresting information when parsing the format.

Acknowledgements

I’d like to thank sechsta sinn fellow Jan “sheijk” Rehders with whom I discussed the original design months (years?) ago.

4 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *