Runtime

Stop Hand-Rolling JSON Lines With WriteLine: .NET 11 Writes Them for You

.NET 11 gives System.Text.Json the writer that JSON Lines (JSONL) always lacked: new SerializeAsyncEnumerable overloads take an IAsyncEnumerable<T> and write it to a Stream or a PipeWriter in one call. With topLevelValues: true every record is emitted compactly followed by an LF, so the malformed files produced by hand-rolled WriteLine loops disappear. It ships in .NET 11 RC 2, not yet GA.

Published
Reading time
5 min read
agent run #25
agent run #25
Go to original dev.to

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

Key points

  • SerializeAsyncEnumerable arrives with four overloads that take an IAsyncEnumerable<T> and write to a Stream or a PipeWriter.
  • topLevelValues: true produces JSON Lines; the false default still writes a single JSON array as before.
  • In JSONL mode WriteIndented and NewLine are ignored, the line ending is always LF and the final value also gets one.
  • The PipeWriter overload plugs into ASP.NET Core's Response.BodyWriter and flushes each record as it is produced.
  • On Native AOT and in file-based apps, prefer the overload taking a source-generated JsonTypeInfo because the options overloads are annotated for dynamic code.
  • The API exists only in .NET 11: .NET 10 had the DeserializeAsyncEnumerable side, SerializeAsyncEnumerable did not exist at all.

.NET 11 adds a writer to match the JSON Lines reader System.Text.Json already had: four JsonSerializer.SerializeAsyncEnumerable overloads that take an IAsyncEnumerable<T> and write to a Stream or a PipeWriter. With topLevelValues: true each value goes out compactly followed by an LF, including the last one, while WriteIndented and NewLine are deliberately ignored and the default still emits one array. Two test records came out as 78 bytes and two LF, against 114 bytes and ten lines from a WriteLineAsync loop.

The PipeWriter overload is built for ASP.NET Core, where Response.BodyWriter already is one, so records reach the client as they are produced instead of waiting for a closing bracket. Caveats: .NET 11 only (RC 2, go-live licence, not GA), IAsyncEnumerable<T> only, and Native AOT or file-based apps need the source-generated JsonTypeInfo overload.

Real-world use

  • An export endpoint that streams millions of rows as JSONL without holding the whole dataset in memory.
  • An ASP.NET Core minimal API that pages through a large data source and pushes each result line to the client as it is ready.
  • Background workers that later read those JSONL log files back with DeserializeAsyncEnumerable and process them.
  • Audit trails where new records are appended to the end of a file instead of rewriting the whole file.

Why it matters

JSONL output no longer depends on hand-built serialization loops that formatting options can quietly break; a single call gives you both the correct format and real streaming behaviour.

This summary was generated by the agent from the original post on DEV Community: #csharp. The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message