Description
EDIT See #67337 (comment) for an API proposal.
Background and motivation
I have a requirement to write large binary content in json.
In order to do this, I need to encode it to base64 before writing.
The resultant json looks like this:
{
"data": "large_base64_encoded_string"
}
I have a PipeReader using which I read bytes in loop and keep appending to a list of bytes.
I then convert the list into a byte array, then convert it to base64 string and use WriteStringValue
to write it.
public void WriteBinaryContent(PipeReader reader, Utf8JsonWriter writer)
{
writer.WriteStartObject();
writer.WritePropertyName("data");
byte[] byteArray = ReadBinaryData(reader).;
string base64data = Convert.ToBase64String(byteArray);
writer.WriteStringValue(base64data);
writer.WriteEndObject();
}
public byte[] ReadBinaryData(PipeReader reader)
{
List<byte> bytes = new List<byte>();
while (reader.TryRead(out ReadResult result))
{
ReadOnlySequence<byte> buffer = result.Buffer;
bytes.AddRange(buffer.ToArray());
// Tell the PipeReader how much of the buffer has been consumed.
reader.AdvanceTo(buffer.End);
// Stop reading if there's no more data coming.
if (result.IsCompleted)
{
break;
}
}
// Mark the PipeReader as complete.
await reader.Complete();
byte[] byteArray = bytes.ToArray();
return byteArray;
}
The problem with this approach is excessive memory consumption. We need to keep the whole binary content in memory, convert to base64 and then write it.
Memory consumption is critical when using Utf8JsonWriter in the override of JsonConverter.Write()
method in a web application.
Instead I am proposing a way to stream large binary content.
API Proposal
namespace System.Text.Json
{
public class Utf8JsonWriter : IAsyncDisposable, IDisposable
{
// This returns a stream on which binary data can be written.
// It will encode it to base64 and write to output stream.
public Stream CreateBinaryWriteStream();
}
}
API Usage
public void WriteBinaryContent(PipeReader reader, Utf8JsonWriter writer)
{
writer.WriteStartObject();
writer.WritePropertyName("data");
using (Stream binaryStream = writer.CreateBinaryWriteStream())
{
StreamBinaryData(reader, binaryStream);
binaryStream.Flush();
}
writer.WriteEndObject();
}
public void StreamBinaryData(PipeReader reader, Stream stream)
{
List<byte> bytes = new List<byte>();
while (reader.TryRead(out ReadResult result))
{
ReadOnlySequence<byte> buffer = result.Buffer;
byte[] byteArray = buffer.ToArray();
stream.Write(byteArray, 0, byteArray.Length);
stream.Flush();
// Tell the PipeReader how much of the buffer has been consumed.
reader.AdvanceTo(buffer.End);
// Stop reading if there's no more data coming.
if (result.IsCompleted)
{
break;
}
}
// Mark the PipeReader as complete.
await reader.Complete();
}
Alternative Designs
No response
Risks
No response