Serialization is a critical aspect of software development, enabling object persistence, data exchange, and state management. While .NET provides robust built-in serialization mechanisms, customization is often required to meet specific application needs, optimize performance, or enforce security policies.
In this post, we will explore advanced serialization techniques in C#, including custom serialization with ISerializable
, attributes like OnSerializing
, contract resolvers in Newtonsoft.Json, and efficient binary serialization approaches.
Understanding Serialization in C#
Serialization is the process of converting an object into a format that can be stored or transmitted and later reconstructed. Common serialization formats include:
Binary Serialization (for compact, performant persistence)
JSON Serialization (widely used for APIs and web services)
XML Serialization (useful for configuration files and interoperability)
Custom Serialization (for tailored control over data persistence)
Choosing the Right Serialization Mechanism
Depending on your use case, you may need to choose between different serializers:
System.Text.Json
(Efficient and built into .NET Core and later)Newtonsoft.Json
(Flexible and powerful, widely used in ASP.NET Core)BinaryFormatter
(Deprecated for security reasons, but useful in legacy applications)DataContractSerializer
(Optimized for WCF services)XmlSerializer
(For XML-based formats)
Customizing JSON Serialization
Using JsonConverter
in System.Text.Json
The JsonConverter<T>
class allows fine-grained control over how objects are serialized and deserialized.
Example: Custom JSON Converter
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class DateOnlyJsonConverter : JsonConverter<DateTime>
{
private const string Format = "yyyy-MM-dd";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTime.ParseExact(reader.GetString(), Format, null);
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(Format));
}
public class Order
{
public string OrderId { get; set; }
[JsonConverter(typeof(DateOnlyJsonConverter))]
public DateTime OrderDate { get; set; }
}
This ensures that DateTime
values are serialized as yyyy-MM-dd
instead of the default ISO-8601 format.
Using Contract Resolvers in Newtonsoft.Json
For more flexibility, you can use IContractResolver
to control serialization behavior dynamically.
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;
public class CustomContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName == "SensitiveData")
{
property.ShouldSerialize = instance => false; // Hide property
}
return property;
}
}
public class User
{
public string Name { get; set; }
public string SensitiveData { get; set; }
}
var settings = new JsonSerializerSettings { ContractResolver = new CustomContractResolver() };
string json = JsonConvert.SerializeObject(new User { Name = "Alice", SensitiveData = "Secret" }, settings);
Here, SensitiveData
will be omitted from the serialized JSON.
Implementing Custom Binary Serialization
Binary serialization is useful for efficient storage and transmission. Instead of using BinaryFormatter
, which is now obsolete, we can use System.IO.MemoryStream
with BinaryWriter
and BinaryReader
.
Example: Custom Binary Serialization
using System;
using System.IO;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Serialize(Stream stream)
{
using var writer = new BinaryWriter(stream);
writer.Write(Name);
writer.Write(Age);
}
public static Person Deserialize(Stream stream)
{
using var reader = new BinaryReader(stream);
return new Person { Name = reader.ReadString(), Age = reader.ReadInt32() };
}
}
This avoids the security risks of BinaryFormatter
while allowing efficient object serialization.
Controlling Serialization with Attributes
C# provides several attributes to control serialization behavior:
[NonSerialized]
(Excludes a field from binary serialization)[JsonIgnore]
(Excludes a property from JSON serialization)[OnSerializing]
,[OnSerialized]
(Hook into the serialization lifecycle)
Example: Using Serialization Callbacks
using System;
using System.Runtime.Serialization;
using System.Text.Json;
[Serializable]
public class Customer
{
public string Name { get; set; }
[NonSerialized]
private int _internalId;
[OnSerializing]
private void OnSerializingMethod(StreamingContext context)
{
Console.WriteLine("Serializing customer...");
}
}
These attributes help customize serialization behavior based on your application’s needs.
Optimizing Performance and Security
1. Use System.Text.Json
Instead of Newtonsoft.Json
When Performance Matters
System.Text.Json
is optimized for .NET Core and provides better performance than Newtonsoft.Json
for large-scale applications.
2. Avoid BinaryFormatter
for Security Reasons
BinaryFormatter
is vulnerable to deserialization attacks and should be replaced with safer alternatives like MemoryStream
or protobuf-net
.
3. Minimize JSON Payload Size
To optimize serialization, remove unnecessary properties using contract resolvers, ignore null values, and use camelCase for property names.
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
4. Use Compression for Large Data Sets
For large serialized objects, use GZipStream
to reduce size before storage or transmission.
using System.IO.Compression;
public static byte[] Compress(byte[] data)
{
using var output = new MemoryStream();
using (var gzip = new GZipStream(output, CompressionMode.Compress))
{
gzip.Write(data, 0, data.Length);
}
return output.ToArray();
}
Conclusion
Customizing serialization in C# is essential for optimizing data persistence, enhancing security, and ensuring application performance. Whether you're working with JSON, binary formats, or custom serialization logic, choosing the right approach can significantly impact efficiency and maintainability.
By leveraging custom converters, contract resolvers, and serialization attributes, you can gain full control over how objects are serialized and deserialized in your .NET applications. Implementing best practices like avoiding BinaryFormatter
, optimizing JSON payloads, and applying compression techniques will further enhance your serialization strategy.