Custom Serialization in C#: Tailor Object Persistence to Your Needs

Serialization is a fundamental concept in C# that enables the transformation of objects into a format suitable for storage or transmission. While built-in serializers like System.Text.Json, XmlSerializer, and BinaryFormatter cover many common scenarios, they often fall short when dealing with complex object graphs, security constraints, or performance requirements.

This blog post delves deep into custom serialization in C#, equipping you with the knowledge to tailor object persistence to your specific needs.

Why Custom Serialization Matters

Custom serialization is essential when:

  • You need full control over the serialization process.

  • Built-in serializers do not support complex scenarios.

  • Performance optimization is critical.

  • You need to ensure data security and compliance.

  • Backward and forward compatibility is required.

Understanding Built-in Serialization Options

Before diving into custom serialization, let's review the built-in serialization mechanisms in .NET:

1. Binary Serialization (Obsolete)

Traditionally, .NET used BinaryFormatter for binary serialization, but it is not recommended due to security vulnerabilities.

[Serializable]
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Note: Avoid BinaryFormatter in new projects; use safer alternatives like JSON or Protobuf.

2. JSON Serialization

.NET Core provides System.Text.Json, a high-performance JSON serializer.

var json = JsonSerializer.Serialize(new Person { Name = "John", Age = 30 });

Limitation: Does not support private fields and requires custom converters for special scenarios.

3. XML Serialization

Useful for interoperability but limited in handling private members.

var xmlSerializer = new XmlSerializer(typeof(Person));

Implementing Custom Serialization

When built-in serializers do not meet your needs, you can implement custom serialization using:

1. Implementing ISerializable Interface

The ISerializable interface provides granular control over how objects are serialized.

[Serializable]
public class Employee : ISerializable
{
    public string Name { get; set; }
    public int Salary { get; set; }

    public Employee() { }
    
    protected Employee(SerializationInfo info, StreamingContext context)
    {
        Name = info.GetString("Name");
        Salary = info.GetInt32("Salary");
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Name", Name);
        info.AddValue("Salary", Salary);
    }
}

Best Practice: Implement ISerializable only when necessary and handle versioning carefully.

2. Custom JSON Serialization with JsonConverter

When System.Text.Json does not support a required format, use JsonConverter.

public class EmployeeJsonConverter : JsonConverter<Employee>
{
    public override Employee Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string name = "";
        int salary = 0;
        
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                string propertyName = reader.GetString();
                reader.Read();
                if (propertyName == "EmployeeName")
                    name = reader.GetString();
                else if (propertyName == "Salary")
                    salary = reader.GetInt32();
            }
        }
        return new Employee { Name = name, Salary = salary };
    }
    
    public override void Write(Utf8JsonWriter writer, Employee value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("EmployeeName", value.Name);
        writer.WriteNumber("Salary", value.Salary);
        writer.WriteEndObject();
    }
}

Use Case: When API contracts require custom JSON structures.

3. Custom XML Serialization with IXmlSerializable

For custom XML formatting, implement IXmlSerializable.

public class Department : IXmlSerializable
{
    public string Name { get; set; }
    public int EmployeesCount { get; set; }

    public XmlSchema GetSchema() => null;

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();
        Name = reader.GetAttribute("Name");
        EmployeesCount = int.Parse(reader.GetAttribute("EmployeesCount"));
        reader.Read();
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("Name", Name);
        writer.WriteAttributeString("EmployeesCount", EmployeesCount.ToString());
    }
}

Best Practice: Ensure XML structure compatibility with external systems.

Performance Optimization Strategies

  1. Use Binary Serialization Alternatives (e.g., MessagePack, Protobuf for high-speed serialization).

  2. Avoid Over-Serialization (Serialize only required properties, avoid deep object graphs).

  3. Compress Serialized Data (Use GZipStream to reduce payload size).

  4. Parallel Serialization (Use async serialization to improve throughput).

Handling Complex Scenarios

1. Circular References

Use ReferenceHandler.Preserve in System.Text.Json:

var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve };

2. Versioning Support

Implement versioning strategies to maintain backward compatibility:

info.AddValue("Version", 1);

3. Secure Serialization

  • Avoid serializing sensitive data.

  • Use cryptographic signing to prevent tampering.

  • Ensure deserialization does not execute arbitrary code.

Real-World Use Cases

  • Enterprise APIs: Custom JSON converters for strict API contract compliance.

  • Caching Strategies: Serialize objects efficiently for in-memory caches.

  • Data Persistence: Store application state with version-aware serialization.

Conclusion

Custom serialization in C# is a powerful technique when built-in options do not meet application needs. By leveraging ISerializable, JsonConverter, and IXmlSerializable, developers gain full control over object persistence, ensuring performance, security, and flexibility. Understanding when and how to implement custom serialization can significantly enhance the maintainability and efficiency of your applications.