Skip to main content

Serialization

Serialization is the process of converting objects into a format that can be stored or transmitted, while deserialization is the reverse process. C# provides several serialization mechanisms for different formats and use cases.

JSON Serialization​

System.Text.Json (Modern)​

// Basic JSON serialization with System.Text.Json
public void BasicJsonSerialization()
{
// Create an object to serialize
var person = new Person
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1),
Address = new Address
{
Street = "123 Main St",
City = "Anytown",
ZipCode = "12345"
},
PhoneNumbers = new List<string> { "555-1234", "555-5678" }
};

// Serialize to JSON string
string json = JsonSerializer.Serialize(person);
Console.WriteLine(json);

// Deserialize from JSON string
Person deserializedPerson = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"Deserialized: {deserializedPerson.Name}");

// Serialize to file
File.WriteAllText("person.json", json);

// Deserialize from file
string fileJson = File.ReadAllText("person.json");
Person fileDeserializedPerson = JsonSerializer.Deserialize<Person>(fileJson);
}

// Customizing JSON serialization
public void CustomJsonSerialization()
{
var options = new JsonSerializerOptions
{
WriteIndented = true, // Pretty printing
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // camelCase property names
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, // Skip null values
Converters = { new JsonStringEnumConverter() } // Serialize enums as strings
};

var person = new Person { Name = "Jane Doe", Status = UserStatus.Active };

string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);

// Deserialize with options
Person deserializedPerson = JsonSerializer.Deserialize<Person>(json, options);
}

// Using JSON attributes
public class Person
{
public int Id { get; set; }

public string Name { get; set; }

[JsonPropertyName("email_address")]
public string Email { get; set; }

[JsonIgnore]
public string Password { get; set; }

[JsonConverter(typeof(JsonStringEnumConverter))]
public UserStatus Status { get; set; }

[JsonInclude]
public DateTime BirthDate { get; private set; }

public Address Address { get; set; }

public List<string> PhoneNumbers { get; set; }
}

public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}

public enum UserStatus
{
Inactive,
Active,
Suspended
}

// Working with JSON documents
public void JsonDocumentExample()
{
string json = @"{
""name"": ""John Doe"",
""age"": 30,
""address"": {
""street"": ""123 Main St"",
""city"": ""Anytown""
},
""phoneNumbers"": [""555-1234"", ""555-5678""]
}";

// Parse the JSON document
using (JsonDocument document = JsonDocument.Parse(json))
{
JsonElement root = document.RootElement;

// Access properties
string name = root.GetProperty("name").GetString();
int age = root.GetProperty("age").GetInt32();

// Access nested properties
JsonElement address = root.GetProperty("address");
string street = address.GetProperty("street").GetString();

// Access array elements
JsonElement phoneNumbers = root.GetProperty("phoneNumbers");
string firstPhone = phoneNumbers[0].GetString();

Console.WriteLine($"Name: {name}, Age: {age}");
Console.WriteLine($"Street: {street}");
Console.WriteLine($"First phone: {firstPhone}");

// Check if a property exists
bool hasEmail = root.TryGetProperty("email", out JsonElement email);

// Enumerate array elements
foreach (JsonElement phone in phoneNumbers.EnumerateArray())
{
Console.WriteLine($"Phone: {phone.GetString()}");
}

// Enumerate object properties
foreach (JsonProperty property in root.EnumerateObject())
{
Console.WriteLine($"Property: {property.Name}, Type: {property.Value.ValueKind}");
}
}
}

// Creating JSON with Utf8JsonWriter
public void CreateJsonWithWriter()
{
using (MemoryStream stream = new MemoryStream())
{
using (Utf8JsonWriter writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
{
writer.WriteStartObject();

writer.WriteString("name", "John Doe");
writer.WriteNumber("age", 30);

writer.WriteStartObject("address");
writer.WriteString("street", "123 Main St");
writer.WriteString("city", "Anytown");
writer.WriteEndObject();

writer.WriteStartArray("phoneNumbers");
writer.WriteStringValue("555-1234");
writer.WriteStringValue("555-5678");
writer.WriteEndArray();

writer.WriteEndObject();
}

string json = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(json);
}
}

// Custom JSON converter
public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException("Expected string value for DateTime");
}

string dateString = reader.GetString();
return DateTime.Parse(dateString);
}

public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-dd"));
}
}

// Using the custom converter
public void UseCustomConverter()
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new DateTimeConverter() }
};

var person = new Person { Name = "John", BirthDate = new DateTime(1980, 1, 1) };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);
}

Newtonsoft.Json (Json.NET)​

// Basic JSON serialization with Newtonsoft.Json
public void BasicNewtonsoftJsonSerialization()
{
var person = new Person
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1)
};

// Serialize to JSON string
string json = JsonConvert.SerializeObject(person);
Console.WriteLine(json);

// Deserialize from JSON string
Person deserializedPerson = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine($"Deserialized: {deserializedPerson.Name}");

// Pretty print
string prettyJson = JsonConvert.SerializeObject(person, Formatting.Indented);
Console.WriteLine(prettyJson);
}

// Customizing Newtonsoft.Json serialization
public void CustomNewtonsoftJsonSerialization()
{
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = { new StringEnumConverter() }
};

var person = new Person { Name = "Jane Doe", Status = UserStatus.Active };

string json = JsonConvert.SerializeObject(person, settings);
Console.WriteLine(json);

// Deserialize with settings
Person deserializedPerson = JsonConvert.DeserializeObject<Person>(json, settings);
}

// Using Newtonsoft.Json attributes
public class PersonWithAttributes
{
public int Id { get; set; }

public string Name { get; set; }

[JsonProperty("email_address")]
public string Email { get; set; }

[JsonIgnore]
public string Password { get; set; }

[JsonConverter(typeof(StringEnumConverter))]
public UserStatus Status { get; set; }

[JsonProperty]
public DateTime BirthDate { get; private set; }

[JsonExtensionData]
public Dictionary<string, JToken> AdditionalData { get; set; }
}

// Working with JObject and LINQ to JSON
public void LinqToJsonExample()
{
string json = @"{
""name"": ""John Doe"",
""age"": 30,
""address"": {
""street"": ""123 Main St"",
""city"": ""Anytown""
},
""phoneNumbers"": [""555-1234"", ""555-5678""]
}";

// Parse JSON to JObject
JObject jObject = JObject.Parse(json);

// Access properties
string name = (string)jObject["name"];
int age = (int)jObject["age"];

// Access nested properties
string street = (string)jObject["address"]["street"];

// Access array elements
string firstPhone = (string)jObject["phoneNumbers"][0];

Console.WriteLine($"Name: {name}, Age: {age}");
Console.WriteLine($"Street: {street}");
Console.WriteLine($"First phone: {firstPhone}");

// Modify properties
jObject["age"] = 31;
jObject["address"]["zipCode"] = "12345";

// Add new properties
jObject["email"] = "john.doe@example.com";

// Remove properties
jObject.Remove("phoneNumbers");

// Convert back to JSON
string modifiedJson = jObject.ToString();
Console.WriteLine(modifiedJson);

// Create a new JObject
JObject newObject = new JObject
{
["name"] = "Jane Doe",
["age"] = 28,
["address"] = new JObject
{
["street"] = "456 Oak Ave",
["city"] = "Othertown"
},
["phoneNumbers"] = new JArray("555-4321", "555-8765")
};

string newJson = newObject.ToString();
Console.WriteLine(newJson);
}

// Custom JSON converter for Newtonsoft.Json
public class CustomDateTimeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new JsonSerializationException("Expected string value for DateTime");
}

string dateString = (string)reader.Value;
return DateTime.Parse(dateString);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((DateTime)value).ToString("yyyy-MM-dd"));
}
}

XML Serialization​

XmlSerializer​

// Basic XML serialization
public void BasicXmlSerialization()
{
var person = new Person
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1),
Address = new Address
{
Street = "123 Main St",
City = "Anytown",
ZipCode = "12345"
},
PhoneNumbers = new List<string> { "555-1234", "555-5678" }
};

// Create XML serializer
XmlSerializer serializer = new XmlSerializer(typeof(Person));

// Serialize to string
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, person);
string xml = writer.ToString();
Console.WriteLine(xml);
}

// Serialize to file
using (FileStream fs = new FileStream("person.xml", FileMode.Create))
{
serializer.Serialize(fs, person);
}

// Deserialize from file
using (FileStream fs = new FileStream("person.xml", FileMode.Open))
{
Person deserializedPerson = (Person)serializer.Deserialize(fs);
Console.WriteLine($"Deserialized: {deserializedPerson.Name}");
}
}

// Using XML attributes
[XmlRoot("Person")]
public class PersonWithXmlAttributes
{
[XmlAttribute("id")]
public int Id { get; set; }

[XmlElement("FullName")]
public string Name { get; set; }

[XmlElement("EmailAddress")]
public string Email { get; set; }

[XmlIgnore]
public string Password { get; set; }

[XmlElement("DOB")]
public DateTime BirthDate { get; set; }

[XmlElement("HomeAddress")]
public Address Address { get; set; }

[XmlArray("ContactNumbers")]
[XmlArrayItem("Phone")]
public List<string> PhoneNumbers { get; set; }

[XmlElement(Order = 1)]
public string FirstElement { get; set; }

[XmlElement(Order = 2)]
public string SecondElement { get; set; }
}

// Customizing XML serialization
public void CustomXmlSerialization()
{
var person = new PersonWithXmlAttributes
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1),
Address = new Address
{
Street = "123 Main St",
City = "Anytown",
ZipCode = "12345"
},
PhoneNumbers = new List<string> { "555-1234", "555-5678" },
FirstElement = "First",
SecondElement = "Second"
};

XmlSerializer serializer = new XmlSerializer(typeof(PersonWithXmlAttributes));

// Set up XML writer settings
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
OmitXmlDeclaration = false,
Encoding = Encoding.UTF8
};

// Serialize with custom settings
using (StringWriter stringWriter = new StringWriter())
using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
{
// Add XML namespaces
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", ""); // Remove default namespace
namespaces.Add("custom", "http://example.com/custom");

serializer.Serialize(xmlWriter, person, namespaces);
string xml = stringWriter.ToString();
Console.WriteLine(xml);
}
}

// Handling complex XML serialization
public void ComplexXmlSerialization()
{
// Serializing a collection
var people = new List<Person>
{
new Person { Id = 1, Name = "John Doe" },
new Person { Id = 2, Name = "Jane Doe" }
};

XmlSerializer collectionSerializer = new XmlSerializer(typeof(List<Person>));

using (StringWriter writer = new StringWriter())
{
collectionSerializer.Serialize(writer, people);
string xml = writer.ToString();
Console.WriteLine(xml);
}

// Serializing derived types
XmlSerializer derivedSerializer = new XmlSerializer(
typeof(Person),
new Type[] { typeof(Employee), typeof(Customer) }
);

var employee = new Employee { Id = 1, Name = "John Doe", EmployeeId = "E12345" };

using (StringWriter writer = new StringWriter())
{
derivedSerializer.Serialize(writer, employee);
string xml = writer.ToString();
Console.WriteLine(xml);
}
}

// Derived classes for XML serialization
[XmlInclude(typeof(Employee))]
[XmlInclude(typeof(Customer))]
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
// Other properties...
}

public class Employee : Person
{
public string EmployeeId { get; set; }
public string Department { get; set; }
}

public class Customer : Person
{
public string CustomerId { get; set; }
public decimal CreditLimit { get; set; }
}

System.Xml.Linq (LINQ to XML)​

// Creating XML with LINQ to XML
public void CreateXmlWithLinq()
{
// Create an XML document
XDocument document = new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"),
new XComment("Person information"),
new XElement("Person",
new XAttribute("id", 1),
new XElement("Name", "John Doe"),
new XElement("Email", "john.doe@example.com"),
new XElement("BirthDate", new DateTime(1980, 1, 1).ToString("yyyy-MM-dd")),
new XElement("Address",
new XElement("Street", "123 Main St"),
new XElement("City", "Anytown"),
new XElement("ZipCode", "12345")
),
new XElement("PhoneNumbers",
new XElement("Phone", "555-1234"),
new XElement("Phone", "555-5678")
)
)
);

// Save to string
string xml = document.ToString();
Console.WriteLine(xml);

// Save to file
document.Save("person_linq.xml");
}

// Querying XML with LINQ to XML
public void QueryXmlWithLinq()
{
// Load XML from file
XDocument document = XDocument.Load("person_linq.xml");

// Get the root element
XElement person = document.Root;

// Get element values
string name = person.Element("Name").Value;
string email = person.Element("Email").Value;

Console.WriteLine($"Name: {name}, Email: {email}");

// Get attribute value
int id = (int)person.Attribute("id");
Console.WriteLine($"ID: {id}");

// Get nested element value
string street = person.Element("Address").Element("Street").Value;
Console.WriteLine($"Street: {street}");

// Query with LINQ
var phoneNumbers = from phone in person.Element("PhoneNumbers").Elements("Phone")
select phone.Value;

Console.WriteLine("Phone numbers:");
foreach (string phone in phoneNumbers)
{
Console.WriteLine($" {phone}");
}

// Modify elements
person.Element("Name").Value = "Jane Doe";

// Add new elements
person.Add(new XElement("Status", "Active"));

// Remove elements
person.Element("PhoneNumbers").Remove();

// Save changes
document.Save("person_modified.xml");
}

// Transforming XML with LINQ
public void TransformXmlWithLinq()
{
XDocument document = XDocument.Load("person_linq.xml");

// Transform to a new XML structure
XDocument transformed = new XDocument(
new XElement("Contact",
new XElement("FullName", (string)document.Root.Element("Name")),
new XElement("ContactInfo",
new XElement("EmailAddress", (string)document.Root.Element("Email")),
new XElement("PhoneNumbers",
from phone in document.Root.Element("PhoneNumbers").Elements("Phone")
select new XElement("Number", phone.Value)
)
),
new XElement("Location",
new XElement("Address", (string)document.Root.Element("Address").Element("Street")),
new XElement("City", (string)document.Root.Element("Address").Element("City")),
new XElement("PostalCode", (string)document.Root.Element("Address").Element("ZipCode"))
)
)
);

Console.WriteLine(transformed.ToString());
}

Binary Serialization​

BinaryFormatter (Legacy)​

// Note: BinaryFormatter is obsolete and not recommended for new code due to security issues
// Basic binary serialization
public void BasicBinarySerialization()
{
// Create a serializable object
[Serializable]
public class SerializablePerson
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }

[NonSerialized]
private string _temporaryData;

public SerializablePerson() { }

public SerializablePerson(int id, string name, DateTime birthDate)
{
Id = id;
Name = name;
BirthDate = birthDate;
}
}

var person = new SerializablePerson(1, "John Doe", new DateTime(1980, 1, 1));

// Serialize to file
using (FileStream fs = new FileStream("person.bin", FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, person);
}

// Deserialize from file
using (FileStream fs = new FileStream("person.bin", FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
SerializablePerson deserializedPerson = (SerializablePerson)formatter.Deserialize(fs);

Console.WriteLine($"Deserialized: {deserializedPerson.Name}, Born: {deserializedPerson.BirthDate}");
}
}

// Custom serialization with ISerializable
[Serializable]
public class CustomSerializablePerson : ISerializable
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }

// Standard constructor
public CustomSerializablePerson(int id, string name, DateTime birthDate)
{
Id = id;
Name = name;
BirthDate = birthDate;
}

// Deserialization constructor
protected CustomSerializablePerson(SerializationInfo info, StreamingContext context)
{
Id = info.GetInt32("PersonId");
Name = info.GetString("FullName");
BirthDate = info.GetDateTime("DOB");
}

// Serialization method
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("PersonId", Id);
info.AddValue("FullName", Name);
info.AddValue("DOB", BirthDate);
}
}

Custom Binary Serialization​

// Custom binary serialization with BinaryReader/BinaryWriter
public void CustomBinarySerialization()
{
var person = new Person
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1)
};

// Serialize to file
using (FileStream fs = new FileStream("person_custom.bin", FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fs))
{
// Write data in a specific order
writer.Write(person.Id);
writer.Write(person.Name);
writer.Write(person.Email ?? string.Empty);
writer.Write(person.BirthDate.Ticks);

// Write a collection
writer.Write(person.PhoneNumbers?.Count ?? 0);
if (person.PhoneNumbers != null)
{
foreach (string phone in person.PhoneNumbers)
{
writer.Write(phone);
}
}
}

// Deserialize from file
using (FileStream fs = new FileStream("person_custom.bin", FileMode.Open))
using (BinaryReader reader = new BinaryReader(fs))
{
Person deserializedPerson = new Person
{
Id = reader.ReadInt32(),
Name = reader.ReadString(),
Email = reader.ReadString(),
BirthDate = new DateTime(reader.ReadInt64())
};

int phoneCount = reader.ReadInt32();
if (phoneCount > 0)
{
deserializedPerson.PhoneNumbers = new List<string>();
for (int i = 0; i < phoneCount; i++)
{
deserializedPerson.PhoneNumbers.Add(reader.ReadString());
}
}

Console.WriteLine($"Deserialized: {deserializedPerson.Name}, Born: {deserializedPerson.BirthDate}");
}
}

Data Contract Serialization​

DataContractSerializer​

// Basic DataContractSerializer usage
public void BasicDataContractSerialization()
{
// Define a data contract
[DataContract(Name = "Person", Namespace = "http://example.com/person")]
public class PersonWithDataContract
{
[DataMember(Name = "id", Order = 1)]
public int Id { get; set; }

[DataMember(Name = "name", Order = 2)]
public string Name { get; set; }

[DataMember(Name = "email", Order = 3, EmitDefaultValue = false)]
public string Email { get; set; }

[DataMember(Name = "birthDate", Order = 4)]
public DateTime BirthDate { get; set; }

[DataMember(Name = "address", Order = 5)]
public AddressWithDataContract Address { get; set; }

[DataMember(Name = "phoneNumbers", Order = 6)]
public List<string> PhoneNumbers { get; set; }

// This property won't be serialized
public string Password { get; set; }
}

[DataContract(Name = "Address")]
public class AddressWithDataContract
{
[DataMember(Name = "street")]
public string Street { get; set; }

[DataMember(Name = "city")]
public string City { get; set; }

[DataMember(Name = "zipCode")]
public string ZipCode { get; set; }
}

var person = new PersonWithDataContract
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1),
Address = new AddressWithDataContract
{
Street = "123 Main St",
City = "Anytown",
ZipCode = "12345"
},
PhoneNumbers = new List<string> { "555-1234", "555-5678" }
};

// Create serializer
DataContractSerializer serializer = new DataContractSerializer(typeof(PersonWithDataContract));

// Serialize to file
using (FileStream fs = new FileStream("person_datacontract.xml", FileMode.Create))
{
serializer.WriteObject(fs, person);
}

// Deserialize from file
using (FileStream fs = new FileStream("person_datacontract.xml", FileMode.Open))
{
PersonWithDataContract deserializedPerson =
(PersonWithDataContract)serializer.ReadObject(fs);

Console.WriteLine($"Deserialized: {deserializedPerson.Name}, Email: {deserializedPerson.Email}");
}
}

// Handling known types
public void DataContractWithKnownTypes()
{
[DataContract]
[KnownType(typeof(EmployeeWithDataContract))]
[KnownType(typeof(CustomerWithDataContract))]
public class PersonWithDataContract
{
[DataMember]
public int Id { get; set; }

[DataMember]
public string Name { get; set; }
}

[DataContract]
public class EmployeeWithDataContract : PersonWithDataContract
{
[DataMember]
public string EmployeeId { get; set; }

[DataMember]
public string Department { get; set; }
}

[DataContract]
public class CustomerWithDataContract : PersonWithDataContract
{
[DataMember]
public string CustomerId { get; set; }

[DataMember]
public decimal CreditLimit { get; set; }
}

var employee = new EmployeeWithDataContract
{
Id = 1,
Name = "John Doe",
EmployeeId = "E12345",
Department = "IT"
};

// Create serializer with known types
DataContractSerializer serializer = new DataContractSerializer(
typeof(PersonWithDataContract),
new Type[] { typeof(EmployeeWithDataContract), typeof(CustomerWithDataContract) }
);

// Serialize to file
using (FileStream fs = new FileStream("employee_datacontract.xml", FileMode.Create))
{
serializer.WriteObject(fs, employee);
}

// Deserialize from file
using (FileStream fs = new FileStream("employee_datacontract.xml", FileMode.Open))
{
PersonWithDataContract deserializedPerson =
(PersonWithDataContract)serializer.ReadObject(fs);

if (deserializedPerson is EmployeeWithDataContract emp)
{
Console.WriteLine($"Employee: {emp.Name}, ID: {emp.EmployeeId}, Dept: {emp.Department}");
}
}
}

DataContractJsonSerializer​

// DataContractJsonSerializer usage
public void DataContractJsonSerialization()
{
var person = new PersonWithDataContract
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1)
};

// Create serializer
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(PersonWithDataContract));

// Serialize to memory stream
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, person);
ms.Position = 0;

// Convert to string
using (StreamReader reader = new StreamReader(ms))
{
string json = reader.ReadToEnd();
Console.WriteLine(json);
}
}

// Serialize to file
using (FileStream fs = new FileStream("person_datacontract.json", FileMode.Create))
{
serializer.WriteObject(fs, person);
}

// Deserialize from file
using (FileStream fs = new FileStream("person_datacontract.json", FileMode.Open))
{
PersonWithDataContract deserializedPerson =
(PersonWithDataContract)serializer.ReadObject(fs);

Console.WriteLine($"Deserialized: {deserializedPerson.Name}, Email: {deserializedPerson.Email}");
}
}

Protocol Buffers (protobuf)​

Using Google.Protobuf​

// Define a .proto file (person.proto)
/*
syntax = "proto3";

package tutorial;

message Person {
int32 id = 1;
string name = 2;
string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
string number = 1;
PhoneType type = 2;
}

repeated PhoneNumber phones = 4;

message Address {
string street = 1;
string city = 2;
string zip_code = 3;
}

Address address = 5;
}
*/

// Using the generated code
public void ProtobufSerialization()
{
// Create a person
var person = new Person
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com"
};

// Add phone numbers
person.Phones.Add(new Person.Types.PhoneNumber
{
Number = "555-1234",
Type = Person.Types.PhoneType.Home
});

person.Phones.Add(new Person.Types.PhoneNumber
{
Number = "555-5678",
Type = Person.Types.PhoneType.Mobile
});

// Add address
person.Address = new Person.Types.Address
{
Street = "123 Main St",
City = "Anytown",
ZipCode = "12345"
};

// Serialize to file
using (FileStream output = File.Create("person.pb"))
{
person.WriteTo(output);
}

// Deserialize from file
Person deserializedPerson;
using (FileStream input = File.OpenRead("person.pb"))
{
deserializedPerson = Person.Parser.ParseFrom(input);
}

Console.WriteLine($"Deserialized: {deserializedPerson.Name}, Email: {deserializedPerson.Email}");
Console.WriteLine($"Phone count: {deserializedPerson.Phones.Count}");
Console.WriteLine($"Address: {deserializedPerson.Address.Street}, {deserializedPerson.Address.City}");
}

Best Practices​

Choosing the Right Serialization Format​

// Serialization format comparison
public void SerializationComparison()
{
/*
Format Selection Guidelines:

JSON:
- Use for web APIs, configuration files, and cross-platform data exchange
- Human-readable and widely supported
- System.Text.Json for modern .NET applications
- Newtonsoft.Json for more features and backward compatibility

XML:
- Use for document-oriented data, configuration files, and SOAP services
- More verbose than JSON but supports namespaces and schemas
- XmlSerializer for simple cases
- LINQ to XML for more complex XML manipulation

Binary:
- Use for internal storage, caching, and when size and performance matter
- Not human-readable but more compact and faster
- Avoid BinaryFormatter due to security concerns
- Consider custom binary serialization or Protocol Buffers

Protocol Buffers:
- Use for high-performance, cross-platform scenarios
- Very compact binary format with schema evolution support
- Requires code generation from .proto files
- Good for microservices and gRPC

DataContractSerializer:
- Use for WCF services and when you need both XML and JSON formats
- More control over serialization than XmlSerializer
- Supports versioning and polymorphism
*/

// Performance comparison example
var person = new Person
{
Id = 1,
Name = "John Doe",
Email = "john.doe@example.com",
BirthDate = new DateTime(1980, 1, 1),
Address = new Address
{
Street = "123 Main St",
City = "Anytown",
ZipCode = "12345"
},
PhoneNumbers = new List<string> { "555-1234", "555-5678" }
};

// Measure JSON serialization
Stopwatch jsonStopwatch = Stopwatch.StartNew();
string json = JsonSerializer.Serialize(person);
jsonStopwatch.Stop();

// Measure XML serialization
Stopwatch xmlStopwatch = Stopwatch.StartNew();
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person));
string xml;
using (StringWriter writer = new StringWriter())
{
xmlSerializer.Serialize(writer, person);
xml = writer.ToString();
}
xmlStopwatch.Stop();

// Compare sizes
Console.WriteLine($"JSON size: {Encoding.UTF8.GetByteCount(json)} bytes");
Console.WriteLine($"XML size: {Encoding.UTF8.GetByteCount(xml)} bytes");

// Compare times
Console.WriteLine($"JSON time: {jsonStopwatch.ElapsedMilliseconds} ms");
Console.WriteLine($"XML time: {xmlStopwatch.ElapsedMilliseconds} ms");
}

Security Considerations​

// Security best practices
public void SerializationSecurity()
{
// 1. Avoid BinaryFormatter
// BinaryFormatter can deserialize arbitrary types and execute code
// Use safer alternatives like System.Text.Json or custom binary serialization

// 2. Validate input before deserialization
public void SafeJsonDeserialization(string json)
{
// Validate that the JSON is well-formed
try
{
using (JsonDocument doc = JsonDocument.Parse(json))
{
// Additional validation if needed
}
}
catch (JsonException)
{
throw new ArgumentException("Invalid JSON format");
}

// Use a safe deserializer
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
MaxDepth = 10 // Prevent stack overflow from deeply nested objects
};

Person person = JsonSerializer.Deserialize<Person>(json, options);

// Validate the deserialized object
if (string.IsNullOrEmpty(person.Name))
{
throw new ArgumentException("Name is required");
}
}

// 3. Use type constraints with generics
public T Deserialize<T>(string json) where T : class, new()
{
return JsonSerializer.Deserialize<T>(json);
}

// 4. Implement ISerializable carefully
[Serializable]
public class SecureSerializable : ISerializable
{
private string _sensitiveData;

public SecureSerializable(string sensitiveData)
{
_sensitiveData = sensitiveData;
}

protected SecureSerializable(SerializationInfo info, StreamingContext context)
{
// Validate data during deserialization
string data = info.GetString("data");
if (string.IsNullOrEmpty(data))
{
throw new SerializationException("Invalid data");
}

_sensitiveData = data;
}

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// Only serialize what's necessary
info.AddValue("data", _sensitiveData);
}
}
}

Versioning and Compatibility​

// Handling versioning and compatibility
public void SerializationVersioning()
{
// 1. Adding new properties (backward compatibility)
[DataContract]
public class PersonV1
{
[DataMember(Order = 1)]
public int Id { get; set; }

[DataMember(Order = 2)]
public string Name { get; set; }
}

[DataContract]
public class PersonV2
{
[DataMember(Order = 1)]
public int Id { get; set; }

[DataMember(Order = 2)]
public string Name { get; set; }

[DataMember(Order = 3, EmitDefaultValue = false)]
public string Email { get; set; } // New property
}

// 2. Renaming properties with JsonPropertyName
[DataContract]
public class PersonV3
{
[DataMember(Order = 1)]
public int Id { get; set; }

[DataMember(Order = 2)]
[JsonPropertyName("name")] // Keep the same JSON property name
public string FullName { get; set; } // Renamed from Name

[DataMember(Order = 3, EmitDefaultValue = false)]
public string Email { get; set; }
}

// 3. Using OnDeserializing and OnDeserialized callbacks
[DataContract]
public class VersionedPerson
{
[DataMember]
public int Id { get; set; }

[DataMember]
public string Name { get; set; }

[DataMember(EmitDefaultValue = false)]
public string Email { get; set; }

[DataMember]
public int Version { get; set; } = 2; // Current version

private bool _isOldVersion;

[OnDeserializing]
private void OnDeserializing(StreamingContext context)
{
// Initialize defaults
_isOldVersion = true;
Email = null;
}

[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_isOldVersion && Version < 2)
{
// Handle migration from old version
if (string.IsNullOrEmpty(Email))
{
Email = $"{Name.ToLower().Replace(" ", ".")}@example.com";
}
}
}
}

// 4. Using optional fields in Protocol Buffers
// In .proto file:
/*
message Person {
int32 id = 1;
string name = 2;
string email = 3; // Optional in v1, required in v2

// New in v2
optional string phone = 4;
}
*/
}

Performance Optimization​

// Performance optimization techniques
public void SerializationPerformance()
{
// 1. Reuse serializer instances
public class JsonSerializerService
{
private static readonly JsonSerializerOptions _options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

public string Serialize<T>(T obj)
{
return JsonSerializer.Serialize(obj, _options);
}

public T Deserialize<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, _options);
}
}

// 2. Use source generation for System.Text.Json
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(Person))]
internal partial class PersonContext : JsonSerializerContext
{
}

// Usage with source generation
var person = new Person { Name = "John Doe" };
string json = JsonSerializer.Serialize(person, PersonContext.Default.Person);
Person deserializedPerson = JsonSerializer.Deserialize(json, PersonContext.Default.Person);

// 3. Use streams instead of strings for large objects
public void SerializeToStream<T>(T obj, Stream stream)
{
JsonSerializer.Serialize(stream, obj);
}

public T DeserializeFromStream<T>(Stream stream)
{
return JsonSerializer.Deserialize<T>(stream);
}

// 4. Use buffer pooling for large serialization operations
public void SerializeWithBufferPooling<T>(T obj, Stream stream)
{
using (var bufferWriter = new ArrayPoolBufferWriter<byte>())
{
using (var writer = new Utf8JsonWriter(bufferWriter))
{
JsonSerializer.Serialize(writer, obj);
}

bufferWriter.WrittenMemory.Span.CopyTo(stream);
}
}

// 5. Consider using Protocol Buffers for high-performance scenarios
// Protocol Buffers are generally faster and produce smaller output
// than JSON or XML serialization
}

// Custom buffer writer using array pooling
public class ArrayPoolBufferWriter<T> : IBufferWriter<T>, IDisposable
{
private T[] _rentedBuffer;
private int _written;

public ArrayPoolBufferWriter(int initialCapacity = 1024)
{
_rentedBuffer = ArrayPool<T>.Shared.Rent(initialCapacity);
_written = 0;
}

public Memory<T> WrittenMemory => _rentedBuffer.AsMemory(0, _written);

public void Advance(int count)
{
_written += count;
}

public Memory<T> GetMemory(int sizeHint = 0)
{
EnsureCapacity(sizeHint);
return _rentedBuffer.AsMemory(_written);
}

public Span<T> GetSpan(int sizeHint = 0)
{
EnsureCapacity(sizeHint);
return _rentedBuffer.AsSpan(_written);
}

private void EnsureCapacity(int sizeHint)
{
if (sizeHint == 0)
sizeHint = 1;

if (_written + sizeHint > _rentedBuffer.Length)
{
int newSize = Math.Max(_rentedBuffer.Length * 2, _written + sizeHint);
T[] newBuffer = ArrayPool<T>.Shared.Rent(newSize);

Array.Copy(_rentedBuffer, newBuffer, _written);

T[] toReturn = _rentedBuffer;
_rentedBuffer = newBuffer;

ArrayPool<T>.Shared.Return(toReturn);
}
}

public void Dispose()
{
if (_rentedBuffer != null)
{
ArrayPool<T>.Shared.Return(_rentedBuffer);
_rentedBuffer = null;
}
}
}

Resources​