XML serialization

Compiler support for [xml] attributes and System.Xml.Serialization library enable a program to write the value of a class object as an XML element, and read data back from XML to class object.

Single class

In the following code the Person class has an [xml] attribute, so the compiler generates code to serialize the name member variable as XML and set the value of the name member variable from an XML element:

using System;
using System.Xml;
using System.Xml.Serialization;

[xml]
public class Person
{
    public Person()
    {
    }
    public Person(const string& name_) : name(name_)
    {
    }
    public inline const string& Name() const
    {
        return name;
    }
    private string name;
}

int main()
{
    // ...
    return 0;
}

The following code creates a Person object and writes its data to an XML element named "person". Then the XML element is appended to an XML document and the document is written to console:

// ...

int main()
{
    Person joe("Joe Coder");
    XmlSerializationContext ctx;
    ctx.SetFlag(XmlSerializationFlags.suppressMetadata);
    Result<System.Xml.Element*> toXmlResult = joe.ToXml("person", ctx);
    if (toXmlResult.Error())
    {
        Console.Error() << toXmlResult.GetErrorMessage() << "\n";
        return 1;
    }
    System.Xml.Element* element = toXmlResult.Value();
    System.Xml.Document document;
    document.AppendChild(element);
    System.Text.CodeFormatter formatter(Console.Out());
    formatter.SetIndentSize(1);
    Result<bool> writeResult = document.Write(formatter);
    if (writeResult.Error())
    {
        Console.Error() << writeResult.GetErrorMessage() << "\n";
        return 1;
    }

    // ...
}

The compiler implements a member function named ToXml that takes care of writing. In this case I don't want any extra bookkeeping data to the XML so I have set the suppressMetadata flag in the ToXml call.

The output of the program will look like this:

<person>
 <name value="Joe Coder"/>
</person>

You can set the name of the root XML element to whatever you like, in this case it's "person". The XML element for each member variable will have the same name as the member variable.

Reading data back into a Person object is not complicated either:

int main()
{
    // ...
    Person person;
    Result<bool> fromXmlResult = person.FromXml(document.DocumentElement());
    if (fromXmlResult.Error())
    {
        Console.Error() << fromXmlResult.GetErrorMessage() << "\n";
        return 1;
    }
    Console.Out() << person.Name() << "\n";
    // ...
}

The compiler implements a member function named FromXml that reads data back from an XML element.

The code above prints the name of the Person object read, so the output will look like this:

Joe Coder

Here is the source of the whole program.

Class with children

Suppose a class object will have child objects that we want to serialize also. In this case a Person may own vehicles. I have created an abstract Vehicle class for them:

using System;
using System.Collections;
using System.Xml;
using System.Xml.Serialization;

public enum Color
{
    white, black, red, blue
}

public string ColorStr(Color color)
{
    switch (color)
    {
        case Color.white: return "white";
        case Color.black: return "black";
        case Color.red: return "red";
        case Color.blue: return "blue";
    }
    return "<unknown color>";
}

[xml]
public abstract class Vehicle
{
    public Vehicle()
    {
    }
    public default virtual ~Vehicle();
    public void SetModel(const string& model_)
    {
        model = model_;
    }
    public inline const string& Model() const
    {
        return model;
    }
    public void SetColor(Color color_)
    {
        color = color_;
    }
    public inline Color GetColor() const
    {
        return color;
    }
    public virtual void Print()
    {
        Console.Out() << ClassName() << "\n";
        Console.Out() << "model: " << model << "\n";
        Console.Out() << "color: " << ColorStr(color) << "\n";
    }
    private string model;
    private Color color;
}

// ...

The Vehicle class has an [xml] attribute so its data will be serialized. The compiler supports also serializing enumeration values. In this case Color is an enumerated type. Each XML serializable class will have a compiler implemented ClassName() member function that returns the full name of the class. Here it is called in the virtual Print() member function.

I have created two concrete XML serializable classes that derive from the abstract Vechicle class, a Bicycle class and a Car class:

// ...

[xml]
public class Bicycle : Vehicle
{
    public Bicycle()
    {
    }
    public inline int Price()
    {
        return price;
    }
    public void SetPrice(int price_)
    {
        price = price_;
    }
    public override void Print()
    {
        base->Print();
        Console.Out() << "price: " << price << "\n";
    }
    private int price;
}

[xml]
public class Car : Vehicle
{
    public Car()
    {
    }
    public void SetRegistrationNumber(const string& registrationNumber_)
    {
        registrationNumber = registrationNumber_;
    }
    public inline const string& RegistrationNumber() const
    {
        return registrationNumber;
    }
    public override void Print()
    {
        base->Print();
        Console.Out() << "registrationNumber: " << registrationNumber << "\n";
    }
    private string registrationNumber;
}

// ...

An [xml] -attributed class may derive from another [xml] -attributed class or have no inheritance at all in which case the compiler will change it to derive from the abstract XmlSerializable class. This is because the XmlSerializable class will be then the ultimate base class of all XML serializable classes and the Cmajor language does not have multiple inheritance.

Now I have changed the Person class to contain a list of UniquePtr s to vehicles:

// ...

[xml]
public class Person
{
    public Person()
    {
    }
    public Person(const string& name_) : name(name_)
    {
    }
    public inline const string& Name() const
    {
        return name;
    }
    public void AddVehicle(Vehicle* vehicle)
    {
        vehicles.Add(UniquePtr<Vehicle>(vehicle));
    }
    public void Print()
    {
        Console.Out() << "Person" << "\n";
        Console.Out() << "name: " << name << "\n";
        Console.Out() << "vehicles:" << "\n";
        for (const auto& vehicle : vehicles)
        {
            vehicle->Print();
        }
    }
    private string name;
    private List<UniquePtr<Vehicle>> vehicles;
}

[nodiscard]
Result<bool> Register()
{
    auto result = Person.Register();
    if (result.Error()) return result;
    result = Bicycle.Register();
    if (result.Error()) return result;
    result = Car.Register();
    if (result.Error()) return result;
    return Result<bool>(true);
}

// ...

An XML serializable class may have a UniquePtr to another XML serializable class or a List of UniquePtr s to XML serializable classes that will be serialized.

When reading Person data back from XML, the serialization library must be able to create concrete vehicle class objects when it sees a UniquePtr to Vechicle . Therefore now the serializable classes have been registered with the serialization library by calling the compiler-implemented Register() member function of each XML serializable class in the Register() function.

Here's now the code to serialize a person and vehicles it owns:

// ...

int main()
{
    Result<bool> registerResult = Register();
    if (registerResult.Error())
    {
        Console.Error() << registerResult.GetErrorMessage() << "\n";
        return 1;
    }
    Person joe("Joe Coder");
    Bicycle* cooper = new Bicycle();
    cooper->SetModel("Cooper");
    cooper->SetColor(Color.blue);
    cooper->SetPrice(1000);
    joe.AddVehicle(cooper);
    Car* porsche = new Car();
    porsche->SetModel("Porsche");
    porsche->SetColor(Color.red);
    porsche->SetRegistrationNumber("ABC-123");
    joe.AddVehicle(porsche);
    Result<System.Xml.Element*> toXmlResult = joe.ToXml("person");
    if (toXmlResult.Error())
    {
        Console.Error() << toXmlResult.GetErrorMessage() << "\n";
        return 1;
    }
    System.Xml.Element* element = toXmlResult.Value();
    System.Xml.Document document;
    document.AppendChild(element);
    System.Text.CodeFormatter formatter(Console.Out());
    formatter.SetIndentSize(1);
    Result<bool> writeResult = document.Write(formatter);
    if (writeResult.Error())
    {
        Console.Error() << writeResult.GetErrorMessage() << "\n";
        return 1;
    }
    // ...
}

I have changed the ToXml call to not to suppress XML metadata, so the program output will now look like this:

<person classId="2029169441" className="Person" objectId="eee6561b-4ab3-e38b-af80-9ba241b5295e">
 <name value="Joe Coder"/>
 <vehicles>
  <item classId="2018315917" className="Bicycle" objectId="8035841f-fcd7-6da7-0182-745376e62d11">
   <model value="Cooper"/>
   <color value="3"/>
   <price value="1000"/>
  </item>
  <item classId="737295636" className="Car" objectId="d607dda7-824e-ccda-2137-8afe117f8579">
   <model value="Porsche"/>
   <color value="2"/>
   <registrationNumber value="ABC-123"/>
  </item>
 </vehicles>
</person>

The XML elements for the objects contain a classId , className and objectId attributes. The classId attributes enable the serialization library to create instances of corresponding serializable classes when needed. The objectId attribute is not needed yet, because a Person instance owns the Vechicle s. The className attribute is not needed by the library. It has only informative value for the person looking at the XML. When serializing a list of objects, the XML element name of them will be item .

Here's the code to read person data back:

// ...

int main()
{
    // ...

    Person person;
    Result<bool> fromXmlResult = person.FromXml(document.DocumentElement());
    if (fromXmlResult.Error())
    {
        Console.Error() << fromXmlResult.GetErrorMessage() << "\n";
        return 1;
    }
    person.Print();
    return 0;
}

The Person.Print() function will generate the following output:

Person
name: Joe Coder
vehicles:
Bicycle
model: Cooper
color: blue
price: 1000
Car
model: Porsche
color: red
registrationNumber: ABC-123

Here's the whole program.

Network of objects

Suppose we have a network of XML serializable objects and we want to serialize them and their connections as a unit. In this case a Vehicle keeps track of the Person associated with it and Person keeps track of vehicles associated with it but it does not own them. This is the new Vechicle class:

// traffic_3

using System;
using System.Collections;
using System.Xml;
using System.Xml.Serialization;

public enum Color
{
    white, black, red, blue
}

public string ColorStr(Color color)
{
    switch (color)
    {
        case Color.white: return "white";
        case Color.black: return "black";
        case Color.red: return "red";
        case Color.blue: return "blue";
    }
    return "<unknown color>";
}

[xml]
public abstract class Vehicle
{
    public Vehicle()
    {
    }
    public default virtual ~Vehicle();
    public void SetOwner(Person* owner_)
    {
        owner = owner_;
    }
    public void SetModel(const string& model_)
    {
        model = model_;
    }
    public inline const string& Model() const
    {
        return model;
    }
    public void SetColor(Color color_)
    {
        color = color_;
    }
    public inline Color GetColor() const
    {
        return color;
    }
    public virtual void Print()
    {
        Console.Out() << ClassName() << "\n";
        if (!owner.IsNull())
        {
            Console.Out() << "owner: " << owner->Name() << "\n";
        }
        Console.Out() << "model: " << model << "\n";
        Console.Out() << "color: " << ColorStr(color) << "\n";
    }
    private string model;
    private Color color;
    private System.Xml.Serialization.XmlPtr<Person> owner;
}

// ...

I have added an XmlPtr to a Person named owner . An XmlPtr is like an ordinary non-owning pointer, but it has to point to an XML serializable object or be null, and it keeps track of the objectId of the pointed-to object.

The vehicle classes have not changed, but this is the new Person class:

// ...

[xml]
public class Person
{
    public Person()
    {
    }
    public Person(const string& name_) : name(name_)
    {
    }
    public inline const string& Name() const
    {
        return name;
    }
    public void AddVehicle(Vehicle* vehicle)
    {
        vehicle->SetOwner(this);
        vehicles.Add(System.Xml.Serialization.XmlPtr<Vehicle>(vehicle));
    }
    public void Print()
    {
        Console.Out() << "Person" << "\n";
        Console.Out() << "name: " << name << "\n";
        Console.Out() << "vehicles:" << "\n";
        for (const auto& vehicle : vehicles)
        {
            vehicle->Print();
        }
    }
    private string name;
    private List<System.Xml.Serialization.XmlPtr<Vehicle>> vehicles;
}

[nodiscard]
Result<bool> Register()
{
    auto result = Person.Register();
    if (result.Error()) return result;
    result = Bicycle.Register();
    if (result.Error()) return result;
    result = Car.Register();
    if (result.Error()) return result;
    return Result<bool>(true);
}

// ...

Now a person object does not own the vehicle objects, but has a list of XmlPtr s to them.

The serialization code have been changed also:

// ...

int main()
{
    auto registerResult = Register();
    if (registerResult.Error())
    {
        Console.Error() << registerResult.GetErrorMessage() << "\n";
        return 1;
    }
    System.Xml.Serialization.XmlBundle bundle;
    Person* joe = new Person("Joe Coder");
    bundle.Add(joe);
    Bicycle* cooper = new Bicycle();
    cooper->SetModel("Cooper");
    cooper->SetColor(Color.blue);
    cooper->SetPrice(1000);
    joe->AddVehicle(cooper);
    bundle.Add(cooper);
    Car* porsche = new Car();
    porsche->SetModel("Porsche");
    porsche->SetColor(Color.red);
    porsche->SetRegistrationNumber("ABC-123");
    joe->AddVehicle(porsche);
    bundle.Add(porsche);
    Result<UniquePtr<System.Xml.Document>> xmlDocumentResult = bundle.ToXmlDocument();
    if (xmlDocumentResult.Error())
    {
        Console.Error() << xmlDocumentResult.GetErrorMessage() << "\n";
        return 1;
    }
    System.Xml.Document* document = xmlDocumentResult.Value().Get();
    System.Text.CodeFormatter formatter(Console.Out());
    formatter.SetIndentSize(1);
    Result<bool> writeResult = document->Write(formatter);
    if (writeResult.Error())
    {
        Console.Error() << writeResult.GetErrorMessage() << "\n";
        return 1;
    }
    // ...
}

Here we have an XmlBundle to which the serializable objects have been added to. By default an XmlBundle takes ownership of the objects added to it.

The XmlBundle class has a ToXmlDocument member function, that creates an XML document, serializes the objects it contains as XML elements, adds them to the document, and returns the document.

Here's the XML document created by the bundle:

<xmlBundle>
 <object classId="2029169441" className="Person" objectId="05c6a76c-b2c1-4cc6-bfc4-f266f8b76bd9">
  <name value="Joe Coder"/>
  <vehicles>
   <item objectId="57cbe1dc-da35-39c5-2e74-799cc718529e"/>
   <item objectId="438902f2-a4d7-d1a6-d0b0-777a8a7f6511"/>
  </vehicles>
 </object>
 <object classId="2018315917" className="Bicycle" objectId="57cbe1dc-da35-39c5-2e74-799cc718529e">
  <model value="Cooper"/>
  <color value="3"/>
  <owner objectId="05c6a76c-b2c1-4cc6-bfc4-f266f8b76bd9"/>
  <price value="1000"/>
 </object>
 <object classId="737295636" className="Car" objectId="438902f2-a4d7-d1a6-d0b0-777a8a7f6511">
  <model value="Porsche"/>
  <color value="2"/>
  <owner objectId="05c6a76c-b2c1-4cc6-bfc4-f266f8b76bd9"/>
  <registrationNumber value="ABC-123"/>
 </object>
</xmlBundle>

The object IDs enable the bundle in the reading side to restore object connections.

Here's the code to read a bundle back from XML:

// ...

int main()
{
    // ...

    System.Xml.Serialization.XmlBundle readBundle;
    System.Xml.Serialization.XmlSerializationContext ctx;
    ctx.SetFlag(System.Xml.Serialization.XmlSerializationFlags.failOnNotFoundObjects);
    Result<bool> fromXmlResult = readBundle.FromXml(*document, ctx);
    if (fromXmlResult.Error())
    {
        Console.Error() << fromXmlResult.GetErrorMessage() << "\n";
        return 1;
    }
    if (readBundle.Count() != 3)
    {
        Console.Error() << "three objects expected" << "\n";
        return 1;
    }
    System.Xml.Serialization.XmlSerializable* first = readBundle.Get(0);
    if (first is Person*)
    {
        Person* person = cast<Person*>(first);
        person->Print();
    }
    else
    {
        Console.Error() << "Person object expected" << "\n";
    }
    return 0;
}

The Person.Print() function will generate the following output:

Person
name: Joe Coder
vehicles:
Bicycle
owner: Joe Coder
model: Cooper
color: blue
price: 1000
Car
owner: Joe Coder
model: Porsche
color: red
registrationNumber: ABC-123

Here's the whole program.

Not serializing a member variable

If you don't want to serialize a member variable, declare the member variable with [xml="false"] attribute:

using System;
using System.Xml;
using System.Xml.Serialization;

public class NotSerialized
{
}

[xml]
public class Foo
{
    [xml="false"]
    private NotSerialized* notSerialized;
}

void main()
{
}