1. Introduction
We've already worked with collections. You can't do without them in programming or in life. Imagine you're making a to-do list for the day: buy groceries, call the doctor, pick up an order. You probably won't create a separate notebook for each task. It's way simpler and more logical to put all tasks into one list.
Same in programming: when the number of objects grows — users, orders, messages — we don't create a separate variable for each. Instead we use collections: lists, dictionaries, sets. That lets you conveniently store, iterate and process whole groups of data at once.
Real-life scenarios:
- Storing and exchanging data between applications: your collection of books might migrate from one program to another.
- Caching datasets to disk.
- Sending data over the network (frontend ↔ backend).
- Import/export of information (for example, you decided to add JSON export for your books!).
On an interview: “How would you serialize and save a list of orders?” — it's great if you mention not only single-object serialization but also collection serialization!
2. How collections work during serialization
JSON and collections: a short love story
When you serialize a regular object, it becomes a JSON object like { "field": value }. But when you serialize a list or array, you get a JSON array [ ... ].
Visually:
| C# | JSON |
|---|---|
|
|
|
|
|
|
The main magic: Call JsonSerializer.Serialize() on a collection — and it automatically turns it into an array! The reverse with Deserialize<List<T>>() — and the magic works again.
Mapping between C# collections and JSON arrays
| C# collection type | Example | JSON |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. Example: serializing and deserializing an array of books
Let's start with a minimal example. Take our Book class that we used in previous lectures:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
Now create an array of books, serialize it, save it to a file and load it back.
using System;
using System.IO;
using System.Text.Json;
namespace LibraryApp
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
class Program
{
static void Main()
{
// Create an array of books
Book[] books = new Book[]
{
new Book { Title = "Tri tovarishcha", Author = "Erikh Mariya Remark" },
new Book { Title = "Master i Margarita", Author = "Mikhail Bulgakov" },
new Book { Title = "1984", Author = "Dzhordzh Oruell" }
};
// Serialize the array of books to a string
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(books, options);
Console.WriteLine("JSON of the books array:\n" + json);
// Write to a file (synchronously)
File.WriteAllText("books.json", json);
// Read from the file (synchronously)
string jsonFromFile = File.ReadAllText("books.json");
// Deserialize back to an array
Book[]? booksFromFile = JsonSerializer.Deserialize<Book[]>(jsonFromFile);
// Print the result to the console
Console.WriteLine("\nDeserialized books:");
if (booksFromFile != null)
{
foreach (var book in booksFromFile)
{
Console.WriteLine($"- {book.Title} (author: {book.Author})");
}
}
}
}
}
What's happening here?
- We create a Book[], fill it with three books.
- Using JsonSerializer.Serialize we turn the array into a pretty JSON string (the WriteIndented option makes the formatting readable).
- We write it to a file, read it back and deserialize — and now we have the array of books again!
- We verify that all data was restored correctly.
Check that the file "books.json" appears in the program folder. Open it — you'll see roughly this JSON:
[
{
"Title": "Tri tovarishcha",
"Author": "Erikh Mariya Remark"
},
{
"Title": "Master i Margarita",
"Author": "Mikhail Bulgakov"
},
{
"Title": "1984",
"Author": "Dzhordzh Oruell"
}
]
4. Example: serializing a List<T> collection
Working with List<Book> is no different. The serializer sees the collection and turns it into a JSON array.
List<Book> myBooks = new List<Book>
{
new Book { Title = "Prestuplenie i nakazanie", Author = "Fyodor Dostoevskiy" },
new Book { Title = "Voyna i mir", Author = "Lev Tolstoy" }
};
string jsonList = JsonSerializer.Serialize(myBooks, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonList);
// And we deserialize like this:
List<Book>? loadedBooks = JsonSerializer.Deserialize<List<Book>>(jsonList);
// Check that everything came back!
foreach (var book in loadedBooks!)
{
Console.WriteLine($"{book.Title} ({book.Author})");
}
Can you serialize just a list of integers?
Of course! Serialization works not only for your classes but also for primitive types:
List<int> numbers = new List<int> { 10, 20, 30, 40 };
string jsonNums = JsonSerializer.Serialize(numbers); // result: [10,20,30,40]
List<int>? loadedNums = JsonSerializer.Deserialize<List<int>>(jsonNums);
// loadedNums: List<int> with the same values
5. Diagram and analogies: how the serializer sees collections
To better understand how collection serialization works, look at a simple diagram:
graph TD;
A[List[Book] in C#] -->|Serialize| B[JSON array as a string]
B -->|Write| C[File books.json]
C -->|Read| D[JSON string from file]
D -->|Deserialize| E[List[Book] in C#]
Briefly describing the process:
- A collection or array is serialized as a JSON array ([ ... ]).
- The order of elements is preserved (unless you apply special settings).
- Each element of the collection is serialized as its own object.
6. Particulars of serializing collections: what to watch out for
1. null and empty collections
If a collection is null, the serializer by default will write null into JSON. Be careful if in your business logic an empty collection and null are not the same!
If a collection is empty (new List<Book>()), the JSON will be [] — an empty array. That's convenient when you want to explicitly show there are no items.
2. Deserialization — order matters
The order of elements in the array is always preserved. So if you serialized three books in a certain order, they'll be restored in the same order.
3. A collection with objects of different types?
System.Text.Json doesn't support polymorphism "out of the box". So if you have, for example, a List<Animal> with dogs and cats (each a derived class), you can't by default restore which exact type each item was. We'll cover polymorphic serialization later, but for regular lists — it's simple.
7. Typical mistakes and common misunderstandings
Mistake #1: You serialize a collection but forget that it may contain objects with private fields
JsonSerializer serializes only public properties (with getters/setters). If your class looks like this:
public class User
{
public string Login { get; set; }
private string Password { get; set; } // Will not be serialized!
}
The password won't get into JSON, and that's good for security. But if you intended to save it — don't forget to make the property public.
Mistake #2: Serializing collections with null elements
If the collection contains null, for example: new List<Book> { null, book2 }, then after serialization the first element will be null. During deserialization — the same! Example JSON:
[null, { "Title": "Voyna i mir", "Author": "Lev Tolstoy" }]
In practice this is rare, but if you serialize a collection that may have "holes" — handle that in your logic.
Mistake #3: Wrong type during deserialization
A common typo: you serialized a list but deserialize into an array (or vice versa). This works if types are compatible (Book[] ↔ List<Book>), but sometimes there are nuances — e.g., you try to deserialize an array of strings into a single object instead of a collection.
GO TO FULL VERSION