Most of the time, when serializing JSON, you already have access to the underlying object that you are serializing.

Conversely, when de-serializing JSON, you usually have access to the JSON as a string.

There are, however, scenarios when you do not yet have access to the underling object or string - but you have the data in a stream of some sort - either a stream in memory (MemoryStream) or an IO stream of some sort - a FileStream or a NetworkStream.

Traditionally this has been a challenge, as you would have to get access to the underling data structures first.

But from .NET 6 you can perform these operations directly - you can serialize from a stream as well as serializing to a stream.

We will use our usual example class:

public record Animal
{
    public string Name { get; init; }
    public byte Legs { get; init; }
}

First we construct an array of animals

// Create an array of animals
var animals = new[]
{
    new Animal(){ Name = "Dog" , Legs = 4},
    new Animal(){ Name = "Cat" , Legs = 4},
    new Animal(){ Name = "Chicken" , Legs = 2}
};

Next we write the code to serialize the array to a stream, in this case we make use of the fact that the Console in fact has an underlying stream that we can capture.

// Construct the serialization options, we want to camel-case the attributes
var options = new JsonSerializerOptions() 
    { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase };

// Stream to standard output
using (var stream = Console.OpenStandardOutput())
{
    JsonSerializer.Serialize(stream, animals, options);
}

If we run this code, we should see the following printed to the console:

[
  {
    "name": "Dog",
    "legs": 4
  },
  {
    "name": "Cat",
    "legs": 4
  },
  {
    "name": "Chicken",
    "legs": 2
  }
]

The reverse, deserialization, is pretty similar.

Here we will use a file, animals.json, with the following content:

[
    {
      "name": "Monkey",
      "legs": 4
    },
    {
      "name": "Donkey",
      "legs": 4
    },
    {
      "name": "Grasshopper",
      "legs": 6
    }
]

The logic here is to read this file into a stream, to demonstrate the fact that we can serialize from a stream.

// Load the file into a stream
using (var fileStream = new FileStream("animals.json", FileMode.Open))
{
    var newAnimals = JsonSerializer.Deserialize<Animal []>(fileStream, options);
    foreach (var animal in newAnimals)
        Console.WriteLine($"I am {animal.Name} with {animal.Legs} legs");
}

This should print the following:

I am Monkey with 4 legs
I am Donkey with 4 legs
I am Grasshopper with 6 legs

Now this is technically possible in .NET 5, but you have to do this using the async version of this API - DeserializeAsync - a synchronous version is not available.

.NET 6 has both the sync and the async versions available to work with a stream.

Thoughts

This will make it a lot easier to write performant code in scenarios where the data source or target is a stream.

The code is in my Github.

TLDR

There are new overloads to support JSON serialization and de-serialization to and from stream objects.

This is Day 28 of the 30 Days Of .NET 6 where every day I will attempt to explain one new / improved thing in the new release of .NET 6.

Happy hacking!