A Complete Guide to JSON, Text Files, and Stream Handling in Modern .NET
Input/Output (I/O) operations are the backbone of almost every application, web APIs, games, desktop tools, microservices, IoT devices, analytics engines, and backend systems all rely on efficient reading and writing of data.
While I/O used to be a source of frustration in .NET – complex buffering, verbose file APIs, blocking calls etc, modern C# has transformed it into a clean, intuitive, high-performance ecosystem.
C# 14 and .NET 10 continue this transformation with optimised async pipelines, System.Text.Json improvements, smarter buffering, better stream abstractions, and unified patterns across file I/O.
This tutorial explores the three core pillars of modern .NET I/O:
- JSON Handling (fast, efficient, zero-allocation parsing where possible)
- Text File Processing (async, safe, memory-aware API usage)
- Streams (the foundation of all I/O – networking, files, compression, pipelines)
By the end of this tutorial, you’ll understand how to write I/O code that is:
- Fast
- Safe
- Memory-efficient
- Modern and idiomatic
- Easy to maintain
- Leverages .NET 10 improvements
Let’s go deep.
🔥 1. JSON I/O in Modern .NET
Fast, Low-Allocation, High-Throughput JSON with System.Text.Json
JSON is the dominant interchange format for APIs and web services. In earlier .NET versions we used Newtonsoft.Json, but modern .NET ships with System.Text.Json, built for:
- Extreme performance
- Low allocations
- Source-generated serialization
- Built-in UTF-8 support
- Streaming access
- Fully async APIs
⭐ Recommended Approach for .NET 10
Use JsonSerializer for standard scenarios and Utf8JsonReader/Writer for high-performance streaming.
Serialize an object to JSON (async)
var person = new Person("James", 58);
await using var stream = File.Create("person.json");
await JsonSerializer.SerializeAsync(stream, person);
You avoid storing the JSON in memory – the object is written directly to the file stream.
Deserialize JSON from a file (async)
await using var stream = File.OpenRead("person.json");
var person = await JsonSerializer.DeserializeAsync<Person>(stream);
Efficient, UTF-8 native, and avoids buffering the entire file into memory.
Pretty-printing JSON
var options = new JsonSerializerOptions { WriteIndented = true };
await using var stream = File.Create("pretty.json");
await JsonSerializer.SerializeAsync(stream, person, options);
Great for config files, logs, or user-visible JSON.
High-performance streaming JSON reading
When dealing with:
- massive JSON files
- continuous JSON feeds
- IoT telemetry
- log ingestion
- real-time pipelines
Use Utf8JsonReader.
await using var stream = File.OpenRead("data.json");
var buffer = new byte[4096];
var json = new Utf8JsonReader(buffer);
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
{
json = new Utf8JsonReader(buffer.AsSpan(0, bytesRead), json.IsFinalBlock, json.CurrentState);
while (json.Read())
{
// Process tokens
}
}
This is zero-alloc, span-based, and stream-friendly — ideal for large-scale performance-critical systems.
Source-generated JSON (maximum performance)
[JsonSerializable(typeof(Person))]
public partial class PersonContext : JsonSerializerContext { }
Then:
await JsonSerializer.SerializeAsync(stream, person, PersonContext.Default.Person);
Faster startup, fewer allocations, tight AOT compatibility.
📄 2. Text File I/O in .NET 10
Async, safe, UTF-8 by default, and file-system friendly
Text is the simplest form of I/O – logs, config files, CSVs, templates, scripts.
C# 14 continues the pattern of providing clean, async-first APIs that are easy to use and secure by default.
Write all text (async)
await File.WriteAllTextAsync("log.txt", "Hello World");
Simplest and safest.
Append text
await File.AppendAllTextAsync("log.txt", "New entry\n");
Great for log files.
Read all text (async)
string content = await File.ReadAllTextAsync("config.txt");
Line-by-line reading (streaming)
Best for:
- huge files
- CSVs
- log files
- memory-sensitive scenarios
using var reader = File.OpenText("bigfile.txt");
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
Console.WriteLine(line);
}
Efficient writing using StreamWriter
await using var stream = File.Create("output.txt");
await using var writer = new StreamWriter(stream);
await writer.WriteLineAsync("Line 1");
await writer.WriteLineAsync("Line 2");
Uses internal buffering for high throughput.
🔄 3. Streams — The Heart of Modern I/O
Everything in .NET is a stream – files, network sockets, pipes, memory buffers.
Streams provide:
- buffering
- async read/write
- pipelines
- composition (e.g., GZipStream → FileStream → NetworkStream)
- cancellation support
Reading bytes from a stream
await using var fs = File.OpenRead("image.png");
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = await fs.ReadAsync(buffer)) > 0)
{
// process buffer
}
Writing bytes to a stream
await using var fs = File.Create("copy.png");
await fs.WriteAsync(data);
Chaining streams – compression example
await using var fs = File.Create("compressed.gz");
await using var gzip = new GZipStream(fs, CompressionLevel.SmallestSize);
await gzip.WriteAsync(data);
MemoryStream for in-memory I/O
using var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, person);
byte[] bytes = ms.ToArray();
Used extensively in testing, APIs, and blob generation.
NetworkStream example
(Works with TCP clients, WebSockets, WebSocketStream, etc.)
await using var ns = tcpClient.GetStream();
byte[] buffer = Encoding.UTF8.GetBytes("Hello");
await ns.WriteAsync(buffer);
🧱 Best Practices for Modern I/O
1. Always use async I/O
Prevents thread starvation and scales better.
2. Prefer UTF-8 everywhere
.NET 10 uses UTF-8 as the default encoding in many APIs.
3. Avoid blocking I/O on the main/UI thread
No .Result
No .GetAwaiter().GetResult()
4. Reuse buffers for performance
Use:
ArrayPool<byte>.Shared- spans
- pooled writers
5. Use streaming for large files
Never do:
File.ReadAllBytes("10GBfile.dat");
6. Prefer System.Text.Json over Newtonsoft.Json
Unless:
- you need advanced polymorphic handling
- you consume external JSON that breaks STJ rules
7. Use await using
Ensures early flush + disposal.
🧠 Summary Table
| Scenario | API | Notes |
|---|---|---|
| Serialize JSON to file | JsonSerializer.SerializeAsync | Fast, UTF-8, async |
| Read JSON file | DeserializeAsync | Automatically infers types |
| Pretty JSON | JsonSerializerOptions { WriteIndented = true } | Human-friendly |
| Read text file | File.ReadAllTextAsync | Simple |
| Stream lines | StreamReader.ReadLineAsync | Best for large files |
| Write text | File.WriteAllTextAsync | Easy & safe |
| File streaming | FileStream | Real-time processing |
| Compression | GZipStream | Chained streams |
| High-performance JSON | Utf8JsonReader | Zero allocation |
Final Thoughts — The Modern .NET I/O Mindset
Modern C# I/O isn’t just a collection of APIs — it’s a unified, async-first, streaming-first design philosophy.
.NET 10 and C# 14 make JSON faster, text handling simpler, and stream handling more consistent and composable than ever.
If you master:
JsonSerializer- async file APIs
- streaming with
FileStream/NetworkStream Utf8JsonReaderfor high performance
…you’ve mastered the backbone of real-world .NET engineering.