Skip to content

Commit

Permalink
update based on review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
ElizabethOkerio committed Jan 31, 2024
1 parent 758738c commit aea2d9d
Showing 1 changed file with 161 additions and 10 deletions.
171 changes: 161 additions & 10 deletions src/Microsoft.OData.Core/Json/ODataUtf8JsonWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
using Microsoft.OData.Edm;
using System.Text.Unicode;
using System.Buffers.Text;
using System.Net.NetworkInformation;
using System.Data.Common;

namespace Microsoft.OData.Json
{
Expand Down Expand Up @@ -86,6 +84,11 @@ internal sealed class ODataUtf8JsonWriter : IJsonWriter, IDisposable, IJsonWrite
/// </summary>
private bool isWritingAtStartOfArray = false;
/// <summary>
/// Whether we're about to write the first element in array. This helps
/// decide whether we should manually write a separator.
/// </summary>
private bool isWritinglargeByteArrayAtStartOfArray = false;
/// <summary>
/// Whether we're currently writing a raw value at the start of an array
/// or preceeded by a sequence of consecutive raw values at the start of
/// an array e.g. ["rawValue1", "rawValue2", "rawValue3"
Expand Down Expand Up @@ -401,9 +404,15 @@ public void WriteValue(string value)
/// </remarks>
private void WriteStringValueInChunks(ReadOnlySpan<char> value)
{
this.CommitUtf8JsonWriterContentsToBuffer();
this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);
this.CommitUtf8JsonWriterContentsToBuffer();
this.Flush();
if (IsInArray() && !isWritingAtStartOfArray)
{
// Place a separator before the raw value if
// this is an array, unless this is the first item in the array.
this.bufferWriter.Write(itemSeparator.Slice(0, 1).Span);
}
this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);

int chunkSize = 2048;

Expand Down Expand Up @@ -442,6 +451,82 @@ private void WriteStringValueInChunks(ReadOnlySpan<char> value)
// since we bypass the Utf8JsonWriter, we need to signal to other
// Write methods that a separator should be written first
CheckIfSeparatorNeeded();

if (this.isWritingAtStartOfArray || this.isWritingConsecutiveRawValuesAtStartOfArray)
{
this.isWritingConsecutiveRawValuesAtStartOfArray = true;
}

this.isWritingAtStartOfArray = false;
}

/// <summary>
/// Writes a string value into the buffer in chunks asynchronously, handling escaping if necessary.
/// </summary>
/// <param name="value">The string value to write.</param>
/// <remarks>
/// This method writes the provided string value into the buffer in manageable chunks to avoid
/// excessive memory allocations and buffer overflows. If the string requires escaping, it is
/// processed accordingly to ensure correct JSON formatting.
/// </remarks>
private async ValueTask WriteStringValueInChunksAsync(ReadOnlyMemory<char> value)
{
this.CommitUtf8JsonWriterContentsToBuffer();
await this.FlushAsync();

if (IsInArray() && !isWritingAtStartOfArray)
{
// Place a separator before the raw value if
// this is an array, unless this is the first item in the array.
this.bufferWriter.Write(itemSeparator.Slice(0, 1).Span);
}

this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);

int chunkSize = 2048;

for (int i = 0; i < value.Length; i += chunkSize)
{
int remainingChars = Math.Min(chunkSize, value.Length - i);
bool isFinalBlock = (i + remainingChars) == value.Length;
int bytesWritten = 0;

ReadOnlyMemory<char> chunk = value.Slice(i, remainingChars);

int firstIndexToEscape = NeedsEscaping(chunk.Span);

Debug.Assert(firstIndexToEscape >= -1 && firstIndexToEscape < value.Length);

if (firstIndexToEscape != -1)
{
if (bytesWritten > 0 && bytesWritten < chunk.Length)
{
chunk = value.Slice(i - bytesWritten, bytesWritten + chunkSize);
}

WriteEscapedStringChunk(chunk.Span, firstIndexToEscape, isFinalBlock, out bytesWritten);
}
else
{
WriteStringChunk(chunk.Span, isFinalBlock);
}

// Flush the buffer if needed
await this.FlushIfBufferThresholdReachedAsync();
}

this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);

// since we bypass the Utf8JsonWriter, we need to signal to other
// Write methods that a separator should be written first
CheckIfSeparatorNeeded();

if (this.isWritingAtStartOfArray || this.isWritingConsecutiveRawValuesAtStartOfArray)
{
this.isWritingConsecutiveRawValuesAtStartOfArray = true;
}

this.isWritingAtStartOfArray = false;
}

/// <summary>
Expand Down Expand Up @@ -587,7 +672,7 @@ public void WriteValue(byte[] value)
{
this.utf8JsonWriter.WriteBase64StringValue(value);
}

this.FlushIfBufferThresholdReached();
}

Expand All @@ -598,9 +683,17 @@ public void WriteValue(byte[] value)
private void WriteByteValueInChunks(ReadOnlySpan<byte> value)
{
this.CommitUtf8JsonWriterContentsToBuffer();
this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);
this.Flush();

if (IsInArray() && !isWritingAtStartOfArray)
{
// Place a separator before the raw value if
// this is an array, unless this is the first item in the array.
this.bufferWriter.Write(itemSeparator.Slice(0, 1).Span);
}

this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);

int chunkSize = 2732;

int prevBytesNotWritten = 0;
Expand All @@ -622,6 +715,61 @@ private void WriteByteValueInChunks(ReadOnlySpan<byte> value)
// since we bypass the Utf8JsonWriter, we need to signal to other
// Write methods that a separator should be written first
CheckIfSeparatorNeeded();

if (this.isWritingAtStartOfArray || this.isWritingConsecutiveRawValuesAtStartOfArray)
{
this.isWritingConsecutiveRawValuesAtStartOfArray = true;
}

this.isWritingAtStartOfArray = false;
}

/// <summary>
/// Writes the byte value represented by the provided ReadOnlySpan in chunks using Base64 encoding.
/// </summary>
/// <param name="value">The ReadOnlySpan containing the byte value to be written.</param>
private async ValueTask WriteByteValueInChunksAsync(ReadOnlyMemory<byte> value)
{
this.CommitUtf8JsonWriterContentsToBuffer();
await FlushAsync().ConfigureAwait(false);

if (IsInArray() && !isWritingAtStartOfArray)
{
// Place a separator before the raw value if
// this is an array, unless this is the first item in the array.
this.bufferWriter.Write(itemSeparator.Slice(0, 1).Span);
}

this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);

int chunkSize = 2732;

int prevBytesNotWritten = 0;

for (int i = 0; i < value.Length; i += chunkSize)
{
int remainingChars = Math.Min(chunkSize, value.Length - i);
bool isFinalBlock = (i + remainingChars) == value.Length;

ReadOnlyMemory<byte> chunk = value.Slice(i - prevBytesNotWritten, remainingChars + prevBytesNotWritten);

Base64EncodeAndWriteChunk(chunk.Span, isFinalBlock, out prevBytesNotWritten);

await FlushIfBufferThresholdReachedAsync().ConfigureAwait(false);
}

this.bufferWriter.Write(doubleQuote.Slice(0, 1).Span);

// since we bypass the Utf8JsonWriter, we need to signal to other
// Write methods that a separator should be written first
CheckIfSeparatorNeeded();

if (this.isWritingAtStartOfArray || this.isWritingConsecutiveRawValuesAtStartOfArray)
{
this.isWritingConsecutiveRawValuesAtStartOfArray = true;
}

this.isWritingAtStartOfArray = false;
}

/// <summary>
Expand Down Expand Up @@ -746,6 +894,7 @@ private void EnterArrayScope()
{
this.isWritingConsecutiveRawValuesAtStartOfArray = false;
this.isWritingAtStartOfArray = true;
this.isWritinglargeByteArrayAtStartOfArray = true;
this.bitStack.PushFalse();
}

Expand Down Expand Up @@ -1045,17 +1194,18 @@ public async Task WriteValueAsync(sbyte value)

public async Task WriteValueAsync(string value)
{
this.WriteSeparatorIfNecessary();
if (value == null)
{
this.WriteSeparatorIfNecessary();
this.utf8JsonWriter.WriteNullValue();
}
else if (value.Length > bufferWriter.FreeCapacity)
{
WriteStringValueInChunks(value.AsSpan());
await WriteStringValueInChunksAsync(value.AsMemory());
}
else
{
this.WriteSeparatorIfNecessary();
this.utf8JsonWriter.WriteStringValue(value.ToString());
}

Expand All @@ -1064,17 +1214,18 @@ public async Task WriteValueAsync(string value)

public async Task WriteValueAsync(byte[] value)
{
this.WriteSeparatorIfNecessary();
if (value == null)
{
this.WriteSeparatorIfNecessary();
this.utf8JsonWriter.WriteNullValue();
}
else if (value.Length > bufferWriter.Capacity)
{
WriteByteValueInChunks(value.AsSpan());
await WriteByteValueInChunksAsync(value);
}
else
{
this.WriteSeparatorIfNecessary();
this.utf8JsonWriter.WriteBase64StringValue(value);
}

Expand Down

0 comments on commit aea2d9d

Please sign in to comment.