Spooksoft.Xml.Serialization 1.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package Spooksoft.Xml.Serialization --version 1.0.3
NuGet\Install-Package Spooksoft.Xml.Serialization -Version 1.0.3
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Spooksoft.Xml.Serialization" Version="1.0.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Spooksoft.Xml.Serialization --version 1.0.3
#r "nuget: Spooksoft.Xml.Serialization, 1.0.3"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Spooksoft.Xml.Serialization as a Cake Addin
#addin nuget:?package=Spooksoft.Xml.Serialization&version=1.0.3

// Install Spooksoft.Xml.Serialization as a Cake Tool
#tool nuget:?package=Spooksoft.Xml.Serialization&version=1.0.3

Spooksoft.Xml.Serialization

Since there is no good solution for (de)serializing immutable models to/from XML, I wrote my own library, which performs this task.

Usage is very similar to System.Xml.Serialization - even names of the attributes are similar.

[XmlRoot("MyModel")]
public class MyModel 
{
	[XmlElement("MyElement")]
	public int IntProperty { get; set; }

	[XmlAttribute("MyAttribute")]
	public DateTime DateTimeProperty { get; set; }
}

(...)

var serializer = new XmlSerializer();
var ms = new MemoryStream();
var model = new MyModel();

serializer.Serialize(model, ms);

Immutable models

To (de)serialize immutable models, the following requirements must be met:

  • Model must have a single constructor
  • The constructor must have parameters, which match by type (exactly) and by name (case-insensitive) read-only properties you want to serialize

Example:

[XmlRoot("MyImmutableModel")]
public class MyImmutableModel 
{
	public MyImmutableModel(string stringProp, int intProp) 
	{
		StringProp = stringProp;
		IntProp = intProp;
	}

	public string StringProp { get; }
	public int IntProp { get; }
}

Varying-type properties

If a property is of reference type and can contain an instance of derived type, my serializer will handle that, but you must explicitly specify all possible variants.

public class MyClass
{
    [XmlVariant("Base", typeof(BaseType))]
    [XmlVariant("Derived1", typeof(DerivedType1))]
    [XmlVariant("Derived2", typeof(DerivedType2))]
    public BaseType Prop { get; set; }
}

This can be also achieved differently, by using XmlIncludeDerived attribute on base property type:

[XmlIncludeDerived("Base", typeof(BaseType))]
[XmlIncludeDerived("Derived1", typeof(DerivedType1))]
[XmlIncludeDerived("Derived2", typeof(DerivedType2))]
public class BaseType
{

}

public class MyClass
{
    // No attributes needed here anymore
    public BaseType Prop { get; set; }
}

The same will work as well for collections and maps (see below).

If you use both XmlIncludeDerived and XmlVariant (or XmlArrayItem in case of collections or XmlMapKey/XmlMapValue in case of maps), then all mapped types will be merged.

Note that neither names nor types in the custom mapping lists must not duplicate:

[XmlIncludeDerived("Derived1", typeof(DerivedType1))]
public class BaseType
{

}

public class MyModel
{
    // WRONG! Name Derived1 has already been used
    [XmlVariant("Derived1", typeof(SomeType))]
    // WRONG! Type DerivedType1 has already been mapped
    [XmlVariant("SomeName", typeof(DerivedType1))]
    public BaseType Prop { get; set; }
}

Collections

Collections must be marked with XmlArray attribute. If you want to support various types in the collection, add XmlArrayItem attributes.

So far the following collections are supported:

  • List<T>
  • IReadOnlyList<T> (filled during deserialization with List<T>)
  • T[] (for now, only single-dimensional arrays are supported)
public class MyModel 
{
    [XmlArray]
    [XmlArrayItem("ItemType1", typeof(ItemType1))]
    [XmlArrayItem("ItemType2", typeof(ItemType2))]
    public List<BaseItemType> Collection { get; set; }

    [XmlArray]
    public int[] MyArray { get; set; }
}

Maps

Map properties (e.g. Dictionary<,>) must be marked with XmlMapAttribute attribute. If you want to support various types for either keys or values, add one or more XmlMapKeyAttribute or XmlMapValueAttribute attributes to the property.

So far the following maps are supported:

  • Dictionary<TKey,TValue>
public class MyModel
{
    [XmlMap]
    [XmlMapKey("Base", typeof(BaseKey))]
    [XmlMapKey("Derived", typeof(DerivedKey))]
    [XmlMapValue("Base", typeof(BaseValue))]
    [XmlMapValue("Derived", typeof(DerivedValue))]
    public Dictionary<BaseKey, BaseValue> Dictionary { get; set; }
}

Binary

To serialize binary data, use XmlBinaryAttribute attribute. Data will be serialized in the Base64 format.

So far the following types of properties can be treated as binary:

  • byte[]
public class MyModel
{
    [XmlBinary]
    public byte[] SomeData { get; set; }
}

Custom serialization

If you want to serialize a class in custom way, implement the IXmlSerializable interface (one provided in the library, not the one from System.Xml.Serialization!)

Example:

public class CustomSerializedModel : IXmlSerializable
{
    private int field1;
    private int field2;

    public CustomSerializedModel()
    {

    }

    public CustomSerializedModel(int field1, int field2)
    {
        this.field1 = field1;
        this.field2 = field2;
    }

    public void Read(XmlElement element)
    {
        string? values = element.GetAttribute("Values");

        if (values != null)
        {
            var splitted = values.Split('|');
            if (splitted.Length == 2)
            {
                int value = 0;
                if (int.TryParse(splitted[0], out value))
                    field1 = value;
                if (int.TryParse(splitted[1], out value))
                    field2 = value;
            }
        }
    }

    public void Write(XmlElement element)
    {
        element.SetAttribute("Values", $"{IntProperty1}|{IntProperty2}");
    }

    public int IntProperty1 => field1;
    public int IntProperty2 => field2;
}

Known limitations

  • null value in a string property serialized to an attribute will be deserialized as an empty string. If you want to keep the null value, serialize it to an element instead ([XmlElement(...)]).
  • You need to separately define XmlArray and XmlElement attributes (if you want to specify custom name for array element). You can not store collections inside an attribute. The same applies to maps and binary data.

Development

Pull requests (e.g. bugfixes, implementation of more collection types) are mostly welcome.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.5 69 5/20/2024
1.0.4 73 5/20/2024
1.0.3 90 4/28/2024
1.0.2 83 4/28/2024
1.0.1 90 4/26/2024
1.0.0 100 4/25/2024

Added attribute [XmlIncludeDerived] to simplify working with class hierarchies