Serialization

XML Serialization

XML serialization is part of .NET since the 1.0 release. It uses attributes and under the hood reflection to serialize data into the XML (https://en.wikipedia.org/wiki/XML) format. To use XML serialization you must reference the System.Xml.Serialization namespace. Warning: The XmlSerializer class does not serializes objects that are marked as [Obsolete].

Important attributes and their hierarchy for XML serialization:

  • XmlArray:

    Specifies that the XmlSerializer must serialize a particular class member as an array of XML elements.

  • XmlArrayItem:

    Represents an attribute that specifies the derived types that the XmlSerializer can place in a serialized array.

  • XmlAttribute:

    Specifies that the XmlSerializer must serialize the class member as an XML attribute.

  • XmlElement:

    Indicates that a public field or property represents an XML element when the XmlSerializer serializes or deserializes the object that contains it.

  • XmlEnum:

    Controls how the XmlSerializer serializes an enumeration member.

  • XmlIgnore:

    Instructs the Serialize(TextWriter, Object) method of the XmlSerializer not to serialize the public field or public read/write property value.

  • XmlRoot:

    Controls XML serialization of the attribute target as an XML root element.

  • XmlTextAttribute:

    Indicates to the XmlSerializer that the member must be treated as XML text when the class that contains it is serialized or de-serialized.

XML serialization example

Object model that will be serialized:

[XmlType(Namespace = "myxmlNamespace", TypeName = "SerializedTypeName")]
[XmlRoot(ElementName = "rootName")]
public class ClassToSerialize
{
    [XmlAttribute(AttributeName = "attribute")]
    public string MyAttribute { get; set; }

    //This value will be ignored
    [XmlIgnore]
    public int Ignored { get; set; }

    [XmlElement(ElementName = "subElement")]
    public SubClass SubClass { get; set; }

    [XmlArray(ElementName = "array")]
    [XmlArrayItem(ElementName = "item1", Type = typeof(Item1))]
    [XmlArrayItem(ElementName = "item2", Type = typeof(Item2))]
    public BaseItem[] Array { get; set; }
}

//An enum with custom values
public enum TestEnum
{
    [XmlEnum(Name = "FirstValue")]
    First,
    [XmlEnum(Name = "SecondValue")]
    Second
}

//A sub class, that is part of the root class that needs to be serialized
//NOTE: Complex types, like this, needs to be serialized as XML elements
//It is not possible to serialize these as attributes
public class SubClass
{
    [XmlText]
    public string Text { get; set; }
}

//An abstract class, that will be used in an array
public abstract class BaseItem
{
    [XmlAttribute(AttributeName = "BaseString")]
    public string Value { get; set; }
}

//A derived type
public class Item1 : BaseItem
{
    [XmlAttribute(AttributeName = "Item1Double")]
    public double DoubleValue { get; set; }
}

//Another derived type
public class Item2 : BaseItem
{
    [XmlAttribute(AttributeName = "Item2Int")]
    public int IntValue { get; set; }
}

Note: The types that will be serialied must have public accessibility modifier.

Serialization code:

var data = new ClassToSerialize()
{
    Ignored = 42,
    MyAttribute = "attribute value",
    SubClass = new SubClass
    {
        Text = "xml text"
    },
    Array = new BaseItem[]
    {
        new Item1
        {
            Value = "Item1 Value",
            DoubleValue = 42.42
        },
        new Item2
        {
            IntValue = 1242,
            Value = "Item2 Value"
        }
    }
};
XmlSerializer serializer = new XmlSerializer(typeof(ClassToSerialize), "namespaceName");
using (var stream = File.Create("testfile.xml"))
{
    serializer.Serialize(stream, data);
}

Result XML:

<?xml version="1.0" encoding="utf-8"?>
<rootName 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  attribute="attribute value"
  xmlns="namespaceName">
  <subElement xmlns="myxmlNamespace">xml text</subElement>
  <array xmlns="myxmlNamespace">
    <item1 
      BaseString="Item1 Value"
      Item1Double="42.42" />
    <item2 
      BaseString="Item2 Value"
      Item2Int="1242" />
  </array>
</rootName>

JSON Serialization

The System.Text.Json serializer was introduced in .NET Core 3.0 as a NuGet package and it's inlcuded in .NET 5 and later releases by default. It's designed to be an efficient replacement for Newtonsoft.Json.

Basic usage:

public class Person 
{
    public string Name { get; init; }
    public int Age { get; init; }
}

var person = new Person
{
    Name = "test",
    Age = 30,
}

string jsonString = JsonSerializer.Serialize(person);


//deserialization:
Person deserializedPerson = JsonSerializer.Deserialize<Person>(jsonString);

JSON Serializer options

Serialization and de serialization options can be controlled via the JsonSerializerOptions class. Important properties:

  • bool AllowTrailingCommas:

    Get or sets a value that indicates whether an extra comma at the end of a list of JSON values in an object or array is allowed (and ignored) within the JSON payload being de-serialized.

  • bool WriteIndented:

    Gets or sets a value that indicates whether JSON should use pretty printing. By default, JSON is serialized without any extra white space.

  • bool PropertyNameCaseInsensitive:

    Gets or sets a value that indicates whether a property's name uses a case-insensitive comparison during deserialization. The default value is false.

  • JsonNamingPolicy? PropertyNamingPolicy:

    Gets or sets a value that specifies the policy used to convert a property's name on an object to another format, such as camel-casing, or null to leave property names unchanged.

    Possible built-in values, that can be set:

    • JsonNamingPolicy.CamelCase - propertyName
    • JsonNamingPolicy.KebabCaseLower - property-name
    • JsonNamingPolicy.KebabCaseUpper - PROPERTY-NAME
    • JsonNamingPolicy.SnakeCaseLower - property_name
    • JsonNamingPolicy.SnakeCaseUpper - PROPERTY_NAME

    Custom values can be set, by implementing the JsonNamingPolicy class.

  • bool IncludeFields:

    Gets or sets a value that indicates whether fields are handled during serialization and de-serialization. The default value is false.

  • bool IgnoreReadOnlyProperties:

    Gets or sets a value that indicates whether null values are ignored during serialization and de-serialization. The default value is false.

  • JsonCommentHandling ReadCommentHandling:

    Gets or sets a value that defines how comments are handled during de-serialization.

    Possible values:

    • JsonCommentHandling.Disallow

      Doesn't allow comments within the JSON input. Comments are treated as invalid JSON if found, and a System.Text.Json.JsonException is thrown. This is the default value.

    • JsonCommentHandling.Skip

      Allows comments within the JSON input and ignores them. The System.Text.Json.Utf8JsonReader behaves as if no comments are present.

    • JsonCommentHandling.Allow

      Allows comments within the JSON input and treats them as valid tokens. While reading, the caller can access the comment values.

  • JsonNumberHandling NumberHandling:

    Gets or sets an object that specifies how number types should be handled when serializing or de-serializing.

    Possible values:

    • JsonNumberHandling.Strict

      Numbers will only be read from System.Text.Json.JsonTokenType.Number tokens and will only be written as JSON numbers (without quotes).

    • JsonNumberHandling.AllowReadingFromString

    Numbers can be read from System.Text.Json.JsonTokenType.String tokens. Does not prevent numbers from being read from System.Text.Json.JsonTokenType.Number token.

    • JsonNumberHandling.WriteAsString

      Numbers will be written as JSON strings (with quotes), not as JSON numbers.

    • JsonNumberHandling.AllowNamedFloatingPointLiterals

      The NaN, Infinity and -Infinity System.Text.Json.JsonTokenType.String tokens can be read as floating-point constants, and the System.Single and System.Double values for these constants will be written as their corresponding JSON string representations.

  • JsonIgnoreCondition DefaultIgnoreCondition:

    Gets or sets a value that determines when properties with default values are ignored during serialization or de-serialization. The default value is JsonIgnoreCondition.Never.

    Possible values:

    • JsonIgnoreCondition.Never

      Property is always serialized and de-serialized, regardless of IgnoreNullValues configuration.

    • JsonIgnoreCondition.Always

      Property is always ignored.

    • JsonIgnoreCondition.WhenWritingDefault

      Property is ignored only if it equals the default value for its type.

    • JsonIgnoreCondition.WhenWritingNull

      Property is ignored if its value is null. This is applied only to reference-type properties and fields.

Converters

You can convert complex types into JSON representation, by implementing the JsonConverter<T> abstract class and then adding the converter to the Converters collection of your JsonSerializerOptions instance.

An example converter:

public class CultureInfoConverter : JsonConverter<CultureInfo>
{
    public override CultureInfo? Read(ref Utf8JsonReader reader, 
                                      Type typeToConvert, 
                                      JsonSerializerOptions options)
    {
        return new CultureInfo(reader.GetString()!);
    }

     public override void Write(Utf8JsonWriter writer,
                                CultureInfo value, 
                                JsonSerializerOptions options)
     {
        writer.WriteStringValue(value.Name);
     }
}