diff --git a/samples/LowAllocationWebServer/Shared/SampleServer.cs b/samples/LowAllocationWebServer/Shared/SampleServer.cs index 9e4d68e5c5b..55c612e225d 100644 --- a/samples/LowAllocationWebServer/Shared/SampleServer.cs +++ b/samples/LowAllocationWebServer/Shared/SampleServer.cs @@ -62,7 +62,7 @@ static void WriteResponseForGetJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - var jsonWriter = new JsonWriterUtf8(response, prettyPrint: false); + var jsonWriter = new JsonWriterUtf8(response, true, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < 5; i++) @@ -94,7 +94,7 @@ static void WriteResponseForPostJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - var jsonWriter = new JsonWriterUtf8(response, prettyPrint: false); + var jsonWriter = new JsonWriterUtf8(response, true, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < requestedCount; i++) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf16.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf16.cs deleted file mode 100644 index 39c44d81034..00000000000 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf16.cs +++ /dev/null @@ -1,881 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Buffers; -using System.Buffers.Text; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Text.JsonLab -{ - public struct JsonWriterUtf16 - { - private static readonly char[] s_newLine = Environment.NewLine.ToCharArray(); - readonly bool _prettyPrint; - readonly IBufferWriter _bufferWriter; - - int _indent; - bool _firstItem; - - /// - /// Constructs a JSON writer with a specified . - /// - /// An instance of used for writing bytes to an output channel. - /// Specifies whether to add whitespace to the output text for user readability. - public JsonWriterUtf16(IBufferWriter bufferWriter, bool prettyPrint = false) - { - _bufferWriter = bufferWriter; - _prettyPrint = prettyPrint; - - _indent = -1; - _firstItem = true; - } - - /// - /// Write the starting tag of an object. This is used for adding an object to an - /// array of other items. If this is used while inside a nested object, the property - /// name will be missing and result in invalid JSON. - /// - public void WriteObjectStart() - { - WriteStartUtf16(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBrace); - - _firstItem = true; - _indent++; - } - - private void WriteStartUtf16(int bytesNeeded, byte token) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - - int idx = 0; - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - { - int indent = _indent; - - while (indent-- >= 0) - { - charBuffer[idx++] = (char)JsonConstants.Space; - charBuffer[idx++] = (char)JsonConstants.Space; - } - } - - charBuffer[idx] = (char)token; - _bufferWriter.Advance(bytesNeeded); - } - - /// - /// Write the starting tag of an object. This is used for adding an object to a - /// nested object. If this is used while inside a nested array, the property - /// name will be written and result in invalid JSON. - /// - /// The name of the property (i.e. key) within the containing object. - public void WriteObjectStart(string name) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); // quote {name} quote colon open-brace, hence 4 - WriteStartUtf16(nameSpan, bytesNeeded, JsonConstants.OpenBrace); - - _firstItem = true; - _indent++; - } - - private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, byte token) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - nameSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += nameSpanChar.Length; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - charBuffer[idx++] = (char)JsonConstants.Space; - - charBuffer[idx] = (char)token; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - /// - /// Writes the end tag for an object. - /// - public void WriteObjectEnd() - { - _firstItem = false; - _indent--; - - WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char)), JsonConstants.CloseBrace); - } - - private void WriteEndUtf16(int bytesNeeded, byte token) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx] = (char)token; - _bufferWriter.Advance(bytesNeeded); - } - - /// - /// Write the starting tag of an array. This is used for adding an array to a nested - /// array of other items. If this is used while inside a nested object, the property - /// name will be missing and result in invalid JSON. - /// - public void WriteArrayStart() - { - WriteStartUtf16(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBracket); - - _firstItem = true; - _indent++; - } - - /// - /// Write the starting tag of an array. This is used for adding an array to a - /// nested object. If this is used while inside a nested array, the property - /// name will be written and result in invalid JSON. - /// - /// The name of the property (i.e. key) within the containing object. - public void WriteArrayStart(string name) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); - WriteStartUtf16(nameSpan, bytesNeeded, JsonConstants.OpenBracket); - - _firstItem = true; - _indent++; - } - - /// - /// Writes the end tag for an array. - /// - public void WriteArrayEnd() - { - _firstItem = false; - _indent--; - - WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char)), JsonConstants.CloseBracket); - } - - /// - /// Write a quoted string value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The string value that will be quoted within the JSON data. - public void WriteAttribute(string name, string value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - ReadOnlySpan valueSpan = value.AsSpan(); - int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, valueSpan, bytesNeeded); - } - - private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, ReadOnlySpan valueSpanChar, int bytesNeeded) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - nameSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += nameSpanChar.Length; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - charBuffer[idx++] = (char)JsonConstants.Space; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - valueSpanChar.CopyTo(charBuffer.Slice(idx)); - - charBuffer[idx + valueSpanChar.Length] = (char)JsonConstants.Quote; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - /// - /// Write a signed integer value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The signed integer value to be written to JSON data. - public void WriteAttribute(string name, long value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 - } - - /// - /// Write an unsigned integer value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The unsigned integer value to be written to JSON data. - public void WriteAttribute(string name, ulong value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 - } - - /// - /// Write a boolean value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The boolean value to be written to JSON data. - public void WriteAttribute(string name, bool value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - if (value) - WriteJsonValue(JsonConstants.TrueValue); - else - WriteJsonValue(JsonConstants.FalseValue); - } - - /// - /// Write a value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The value to be written to JSON data. - public void WriteAttribute(string name, DateTime value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteDateTime(value); - } - - /// - /// Write a value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The value to be written to JSON data. - public void WriteAttribute(string name, DateTimeOffset value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteDateTimeOffset(value); - } - - /// - /// Write a value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The value to be written to JSON data. - public void WriteAttribute(string name, Guid value) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteGuid(value); - } - - /// - /// Write a null value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - public void WriteAttributeNull(string name) - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteJsonValue(JsonConstants.NullValue); - } - - /// - /// Writes a quoted string value into the current array. - /// - /// The string value that will be quoted within the JSON data. - public void WriteValue(string value) - { - ReadOnlySpan valueSpan = value.AsSpan(); - int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(char), 2); - WriteValueUtf16(valueSpan, bytesNeeded); - } - - private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - valueSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += valueSpanChar.Length; - - charBuffer[idx] = (char)JsonConstants.Quote; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - /// - /// Write a signed integer value into the current array. - /// - /// The signed integer value to be written to JSON data. - public void WriteValue(long value) - { - WriteValueUtf16(value, CalculateValueBytesNeeded(sizeof(char))); - } - - private void WriteValueUtf16(long value, int bytesNeeded) - { - bool insertNegationSign = false; - if (value < 0) - { - insertNegationSign = true; - value = -value; - bytesNeeded += sizeof(char); - } - - int digitCount = CountDigits((ulong)value); - bytesNeeded += sizeof(char) * digitCount; - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - - int idx = 0; - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - _firstItem = false; - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - if (insertNegationSign) - charBuffer[idx++] = '-'; - - WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); - - _bufferWriter.Advance(bytesNeeded); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CountDigits(ulong value) - { - int digits = 1; - uint part; - if (value >= 10000000) - { - if (value >= 100000000000000) - { - part = (uint)(value / 100000000000000); - digits += 14; - } - else - { - part = (uint)(value / 10000000); - digits += 7; - } - } - else - { - part = (uint)value; - } - - if (part < 10) - { - // no-op - } - else if (part < 100) - { - digits += 1; - } - else if (part < 1000) - { - digits += 2; - } - else if (part < 10000) - { - digits += 3; - } - else if (part < 100000) - { - digits += 4; - } - else if (part < 1000000) - { - digits += 5; - } - else - { - Debug.Assert(part < 10000000); - digits += 6; - } - - return digits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDigitsUInt64D(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - Debug.Assert(CountDigits(value) == buffer.Length); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (char)(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = (char)('0' + value); - } - - /// - /// Write a unsigned integer value into the current array. - /// - /// The unsigned integer value to be written to JSON data. - public void WriteValue(ulong value) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteNumber(value); - } - - /// - /// Write a boolean value into the current array. - /// - /// The boolean value to be written to JSON data. - public void WriteValue(bool value) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - if (value) - WriteJsonValue(JsonConstants.TrueValue); - else - WriteJsonValue(JsonConstants.FalseValue); - } - - /// - /// Write a value into the current array. - /// - /// The value to be written to JSON data. - public void WriteValue(DateTime value) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteDateTime(value); - } - - /// - /// Write a value into the current array. - /// - /// The value to be written to JSON data. - public void WriteValue(DateTimeOffset value) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteDateTimeOffset(value); - } - - /// - /// Write a value into the current array. - /// - /// The value to be written to JSON data. - public void WriteValue(Guid value) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteGuid(value); - } - - /// - /// Write a null value into the current array. - /// - public void WriteNull() - { - int charsNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0); - int bytesNeeded = charsNeeded * 2 + JsonConstants.NullValueUtf16.Length; - Span byteBuffer = EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(byteBuffer); - int idx = 0; - if (_firstItem) - { - _firstItem = false; - } - else - { - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - } - - if (_prettyPrint) - { - int indent = _indent; - charBuffer[idx++] = (char)JsonConstants.CarriageReturn; - charBuffer[idx++] = (char)JsonConstants.LineFeed; - - while (indent-- >= 0) - { - charBuffer[idx++] = (char)JsonConstants.Space; - charBuffer[idx++] = (char)JsonConstants.Space; - } - } - - Debug.Assert(byteBuffer.Slice(idx * 2).Length >= JsonConstants.NullValueUtf16.Length); - JsonConstants.NullValueUtf16.CopyTo(byteBuffer.Slice(idx * 2)); - - _bufferWriter.Advance(bytesNeeded); - } - - private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - nameSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += nameSpanChar.Length; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - charBuffer[idx] = (char)JsonConstants.Space; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteControlUtf16(byte value) - { - var buffer = EnsureBuffer(2); - Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; - _bufferWriter.Advance(2); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNumber(long value) - { - //TODO: Optimize, this is too slow - var buffer = _bufferWriter.GetSpan(); - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) - buffer = EnsureBuffer(); - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNumber(ulong value) - { - var buffer = _bufferWriter.GetSpan(); - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) - buffer = EnsureBuffer(); - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteDateTime(DateTime value) - { - var buffer = _bufferWriter.GetSpan(); - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf16)) - buffer = EnsureBuffer(); - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteDateTimeOffset(DateTimeOffset value) - { - var buffer = _bufferWriter.GetSpan(); - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf16)) - buffer = EnsureBuffer(); - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteGuid(Guid value) - { - var buffer = _bufferWriter.GetSpan(); - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.GuidFormat, SymbolTable.InvariantUtf16)) - buffer = EnsureBuffer(); - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteJsonValue(ReadOnlySpan values) - { - var buffer = _bufferWriter.GetSpan(); - int written; - while (!SymbolTable.InvariantUtf16.TryEncode(values, buffer, out int consumed, out written)) - buffer = EnsureBuffer(); - - _bufferWriter.Advance(written); - } - - // TODO: Once public methods are optimized, remove this. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteItemSeperatorUtf16() - { - if (_firstItem) return; - - WriteControlUtf16(JsonConstants.ListSeperator); - } - - // TODO: Once public methods are optimized, remove this. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteSpacingUtf16(bool newline = true) - { - if (!_prettyPrint) return; - - var indent = _indent; - var bytesNeeded = newline ? 2 : 0; - bytesNeeded += (indent + 1) * 2; - bytesNeeded *= sizeof(char); - - var buffer = EnsureBuffer(bytesNeeded); - var span = MemoryMarshal.Cast(buffer); - ref char utf16Bytes = ref MemoryMarshal.GetReference(span); - int idx = 0; - - if (newline) - { - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.CarriageReturn; - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.LineFeed; - } - - while (indent-- >= 0) - { - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; - } - - _bufferWriter.Advance(bytesNeeded); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Span EnsureBuffer(int needed = 256) - { - Span buffer = _bufferWriter.GetSpan(needed); - if (buffer.Length < needed) - JsonThrowHelper.ThrowOutOfMemoryException(); - - return buffer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateStartBytesNeeded(int numBytes) - { - int bytesNeeded = numBytes; - - if (!_firstItem) - bytesNeeded *= 2; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateEndBytesNeeded(int numBytes) - { - int bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = s_newLine.Length; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateValueBytesNeeded(int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = s_newLine.Length; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = s_newLine.Length; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * extraCharacterCount; - bytesNeeded += MemoryMarshal.AsBytes(span).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = s_newLine.Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * extraCharacterCount; - bytesNeeded += MemoryMarshal.AsBytes(span).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = s_newLine.Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * 3; // quote {name} quote colon, hence 3 - bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlySpan valueSpan, int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = s_newLine.Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * 5; //quote {name} quote colon quote {value} quote, hence 5 - bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; - bytesNeeded += MemoryMarshal.AsBytes(valueSpan).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddNewLineAndIndentation(Span buffer) - { - int offset = 0; - // \r\n versus \n, depending on OS - if (s_newLine.Length == 2) - buffer[offset++] = (char)JsonConstants.CarriageReturn; - - buffer[offset++] = (char)JsonConstants.LineFeed; - - int indent = _indent; - - while (indent-- >= 0) - { - buffer[offset++] = (char)JsonConstants.Space; - buffer[offset++] = (char)JsonConstants.Space; - } - - return offset; - } - } -} diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf8.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf8.cs index 0a7e0166dd2..fda305e3651 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf8.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriterUtf8.cs @@ -11,9 +11,12 @@ namespace System.Text.JsonLab { public struct JsonWriterUtf8 { - private static readonly byte[] s_newLine = Encoding.UTF8.GetBytes(Environment.NewLine); + private static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); + private static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); + readonly bool _prettyPrint; readonly IBufferWriter _bufferWriter; + readonly bool _isUtf8; int _indent; bool _firstItem; @@ -23,10 +26,11 @@ public struct JsonWriterUtf8 /// /// An instance of used for writing bytes to an output channel. /// Specifies whether to add whitespace to the output text for user readability. - public JsonWriterUtf8(IBufferWriter bufferWriter, bool prettyPrint = false) + public JsonWriterUtf8(IBufferWriter bufferWriter, bool isUtf8, bool prettyPrint = false) { _bufferWriter = bufferWriter; _prettyPrint = prettyPrint; + _isUtf8 = isUtf8; _indent = -1; _firstItem = true; @@ -39,7 +43,14 @@ public JsonWriterUtf8(IBufferWriter bufferWriter, bool prettyPrint = false /// public void WriteObjectStart() { - WriteStartUtf8(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBrace); + if (_isUtf8) + { + WriteStartUtf8(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBrace); + } + else + { + WriteStartUtf16(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBrace); + } _firstItem = true; _indent++; @@ -68,6 +79,29 @@ private void WriteStartUtf8(int bytesNeeded, byte token) _bufferWriter.Advance(bytesNeeded); } + private void WriteStartUtf16(int bytesNeeded, byte token) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + + int idx = 0; + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + { + int indent = _indent; + + while (indent-- >= 0) + { + charBuffer[idx++] = (char)JsonConstants.Space; + charBuffer[idx++] = (char)JsonConstants.Space; + } + } + + charBuffer[idx] = (char)token; + _bufferWriter.Advance(bytesNeeded); + } + /// /// Write the starting tag of an object. This is used for adding an object to a /// nested object. If this is used while inside a nested array, the property @@ -76,9 +110,18 @@ private void WriteStartUtf8(int bytesNeeded, byte token) /// The name of the property (i.e. key) within the containing object. public void WriteObjectStart(string name) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); // quote {name} quote colon open-brace, hence 4 - WriteStartUtf8(nameSpan, bytesNeeded, JsonConstants.OpenBrace); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); // quote {name} quote colon open-brace, hence 4 + WriteStartUtf8(nameSpan, bytesNeeded, JsonConstants.OpenBrace); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); // quote {name} quote colon open-brace, hence 4 + WriteStartUtf16(nameSpan, bytesNeeded, JsonConstants.OpenBrace); + } _firstItem = true; _indent++; @@ -119,6 +162,36 @@ private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, by _firstItem = false; } + private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, byte token) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + nameSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += nameSpanChar.Length; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + charBuffer[idx++] = (char)JsonConstants.Space; + + charBuffer[idx] = (char)token; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } + /// /// Writes the end tag for an object. /// @@ -126,8 +199,14 @@ public void WriteObjectEnd() { _firstItem = false; _indent--; - - WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte)), JsonConstants.CloseBrace); + if (_isUtf8) + { + WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), s_newLineUtf8.Length), JsonConstants.CloseBrace); + } + else + { + WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), s_newLineUtf16.Length), JsonConstants.CloseBrace); + } } private void WriteEndUtf8(int bytesNeeded, byte token) @@ -142,6 +221,18 @@ private void WriteEndUtf8(int bytesNeeded, byte token) _bufferWriter.Advance(bytesNeeded); } + private void WriteEndUtf16(int bytesNeeded, byte token) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx] = (char)token; + _bufferWriter.Advance(bytesNeeded); + } + /// /// Write the starting tag of an array. This is used for adding an array to a nested /// array of other items. If this is used while inside a nested object, the property @@ -149,7 +240,14 @@ private void WriteEndUtf8(int bytesNeeded, byte token) /// public void WriteArrayStart() { - WriteStartUtf8(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBracket); + if (_isUtf8) + { + WriteStartUtf8(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBracket); + } + else + { + WriteStartUtf16(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBracket); + } _firstItem = true; _indent++; @@ -163,9 +261,18 @@ public void WriteArrayStart() /// The name of the property (i.e. key) within the containing object. public void WriteArrayStart(string name) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); - WriteStartUtf8(nameSpan, bytesNeeded, JsonConstants.OpenBracket); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); + WriteStartUtf8(nameSpan, bytesNeeded, JsonConstants.OpenBracket); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); + WriteStartUtf16(nameSpan, bytesNeeded, JsonConstants.OpenBracket); + } _firstItem = true; _indent++; @@ -179,7 +286,14 @@ public void WriteArrayEnd() _firstItem = false; _indent--; - WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte)), JsonConstants.CloseBracket); + if (_isUtf8) + { + WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), s_newLineUtf8.Length), JsonConstants.CloseBracket); + } + else + { + WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), s_newLineUtf16.Length), JsonConstants.CloseBracket); + } } /// @@ -189,10 +303,20 @@ public void WriteArrayEnd() /// The string value that will be quoted within the JSON data. public void WriteAttribute(string name, string value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); - int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, valueSpan, bytesNeeded); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); + int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, valueSpan, bytesNeeded); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + ReadOnlySpan valueSpan = value.AsSpan(); + int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, valueSpan, bytesNeeded); + } } private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan valueSpanByte, int bytesNeeded) @@ -240,6 +364,40 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan nameSpanChar, ReadOnlySpan valueSpanChar, int bytesNeeded) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + nameSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += nameSpanChar.Length; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + charBuffer[idx++] = (char)JsonConstants.Space; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + valueSpanChar.CopyTo(charBuffer.Slice(idx)); + + charBuffer[idx + valueSpanChar.Length] = (char)JsonConstants.Quote; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } + /// /// Write a signed integer value along with a property name into the current object. /// @@ -247,10 +405,20 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpanThe signed integer value to be written to JSON data. public void WriteAttribute(string name, long value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf8 + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + WriteNumberUtf8(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf8 + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + WriteNumberUtf16(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 + } } /// @@ -260,10 +428,20 @@ public void WriteAttribute(string name, long value) /// The unsigned integer value to be written to JSON data. public void WriteAttribute(string name, ulong value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf8 + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf8 + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 + } } /// @@ -273,13 +451,26 @@ public void WriteAttribute(string name, ulong value) /// The boolean value to be written to JSON data. public void WriteAttribute(string name, bool value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - if (value) - WriteJsonValue(JsonConstants.TrueValue); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + if (value) + WriteJsonValueUtf8(JsonConstants.TrueValue); + else + WriteJsonValueUtf8(JsonConstants.FalseValue); + } else - WriteJsonValue(JsonConstants.FalseValue); + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + if (value) + WriteJsonValueUtf16(JsonConstants.TrueValue); + else + WriteJsonValueUtf16(JsonConstants.FalseValue); + } } /// @@ -289,10 +480,20 @@ public void WriteAttribute(string name, bool value) /// The value to be written to JSON data. public void WriteAttribute(string name, DateTime value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteDateTime(value); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + WriteDateTime(value); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + WriteDateTime(value); + } } /// @@ -302,10 +503,20 @@ public void WriteAttribute(string name, DateTime value) /// The value to be written to JSON data. public void WriteAttribute(string name, DateTimeOffset value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteDateTimeOffset(value); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + WriteDateTimeOffset(value); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + WriteDateTimeOffset(value); + } } /// @@ -315,10 +526,20 @@ public void WriteAttribute(string name, DateTimeOffset value) /// The value to be written to JSON data. public void WriteAttribute(string name, Guid value) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteGuid(value); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + WriteGuid(value); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + WriteGuid(value); + } } /// @@ -327,10 +548,81 @@ public void WriteAttribute(string name, Guid value) /// The name of the property (i.e. key) within the containing object. public void WriteAttributeNull(string name) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteJsonValue(JsonConstants.NullValue); + if (_isUtf8) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttributeUtf8(nameSpan, bytesNeeded); + WriteJsonValueUtf8(JsonConstants.NullValue); + } + else + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttributeUtf16(nameSpan, bytesNeeded); + WriteJsonValueUtf16(JsonConstants.NullValue); + } + } + + private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + byteBuffer[idx++] = JsonConstants.Quote; + + OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); + Debug.Assert(consumed == nameSpanByte.Length); + if (status != OperationStatus.Done) + { + JsonThrowHelper.ThrowFormatException(); + } + + idx += written; + + byteBuffer[idx++] = JsonConstants.Quote; + + byteBuffer[idx++] = JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + byteBuffer[idx++] = JsonConstants.Space; + + _bufferWriter.Advance(idx); + _firstItem = false; + } + + private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + nameSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += nameSpanChar.Length; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + charBuffer[idx] = (char)JsonConstants.Space; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; } /// @@ -339,9 +631,18 @@ public void WriteAttributeNull(string name) /// The string value that will be quoted within the JSON data. public void WriteValue(string value) { - ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); - int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(byte), 2); - WriteValueUtf8(valueSpan, bytesNeeded); + if (_isUtf8) + { + ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); + int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(byte), 2); + WriteValueUtf8(valueSpan, bytesNeeded); + } + else + { + ReadOnlySpan valueSpan = value.AsSpan(); + int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(char), 2); + WriteValueUtf16(valueSpan, bytesNeeded); + } } private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) @@ -372,6 +673,29 @@ private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) _bufferWriter.Advance(idx); _firstItem = false; } + + private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + valueSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += valueSpanChar.Length; + + charBuffer[idx] = (char)JsonConstants.Quote; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } /// /// Write a signed integer value into the current array. @@ -379,7 +703,112 @@ private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) /// The signed integer value to be written to JSON data. public void WriteValue(long value) { - WriteValueUtf8(value, CalculateValueBytesNeeded(sizeof(byte))); + if (_isUtf8) + { + WriteValueUtf8(value, CalculateValueBytesNeeded(sizeof(byte), s_newLineUtf8.Length)); + } + else + { + WriteValueUtf16(value, CalculateValueBytesNeeded(sizeof(char), s_newLineUtf16.Length)); + } + } + + private void WriteValueUtf8(long value, int bytesNeeded) + { + bool insertNegationSign = false; + if (value < 0) + { + insertNegationSign = true; + value = -value; + bytesNeeded += sizeof(byte); + } + + int digitCount = CountDigits((ulong)value); + bytesNeeded += sizeof(byte) * digitCount; + Span byteBuffer = EnsureBuffer(bytesNeeded); + + int idx = 0; + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + _firstItem = false; + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + if (insertNegationSign) + byteBuffer[idx++] = (byte)'-'; + + WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); + + _bufferWriter.Advance(bytesNeeded); + } + + private void WriteValueUtf16(long value, int bytesNeeded) + { + bool insertNegationSign = false; + if (value < 0) + { + insertNegationSign = true; + value = -value; + bytesNeeded += sizeof(char); + } + + int digitCount = CountDigits((ulong)value); + bytesNeeded += sizeof(char) * digitCount; + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + + int idx = 0; + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + _firstItem = false; + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + if (insertNegationSign) + charBuffer[idx++] = '-'; + + WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); + + _bufferWriter.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteDigitsUInt64D(ulong value, Span buffer) + { + // We can mutate the 'value' parameter since it's a copy-by-value local. + // It'll be used to represent the value left over after each division by 10. + + Debug.Assert(CountDigits(value) == buffer.Length); + + for (int i = buffer.Length - 1; i >= 1; i--) + { + ulong temp = '0' + value; + value /= 10; + buffer[i] = (byte)(temp - (value * 10)); + } + + Debug.Assert(value < 10); + buffer[0] = (byte)('0' + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteDigitsUInt64D(ulong value, Span buffer) + { + // We can mutate the 'value' parameter since it's a copy-by-value local. + // It'll be used to represent the value left over after each division by 10. + + Debug.Assert(CountDigits(value) == buffer.Length); + + for (int i = buffer.Length - 1; i >= 1; i--) + { + ulong temp = '0' + value; + value /= 10; + buffer[i] = (char)(temp - (value * 10)); + } + + Debug.Assert(value < 10); + buffer[0] = (char)('0' + value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -429,62 +858,13 @@ private static int CountDigits(ulong value) { digits += 5; } - else - { - Debug.Assert(part < 10000000); - digits += 6; - } - - return digits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDigitsUInt64D(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - Debug.Assert(CountDigits(value) == buffer.Length); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (byte)(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = (byte)('0' + value); - } - - private void WriteValueUtf8(long value, int bytesNeeded) - { - bool insertNegationSign = false; - if (value < 0) - { - insertNegationSign = true; - value = -value; - bytesNeeded += sizeof(byte); - } - - int digitCount = CountDigits((ulong)value); - bytesNeeded += sizeof(byte) * digitCount; - Span byteBuffer = EnsureBuffer(bytesNeeded); - - int idx = 0; - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - _firstItem = false; - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - if (insertNegationSign) - byteBuffer[idx++] = (byte)'-'; - - WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); + else + { + Debug.Assert(part < 10000000); + digits += 6; + } - _bufferWriter.Advance(bytesNeeded); + return digits; } /// @@ -493,12 +873,24 @@ private void WriteValueUtf8(long value, int bytesNeeded) /// The unsigned integer value to be written to JSON data. public void WriteValue(ulong value) { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); + if (_isUtf8) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf8(); + _firstItem = false; + WriteSpacingUtf8(); + + WriteNumber(value); + } + else + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacingUtf16(); - WriteNumber(value); + WriteNumber(value); + } } /// @@ -507,15 +899,30 @@ public void WriteValue(ulong value) /// The boolean value to be written to JSON data. public void WriteValue(bool value) { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); + if (_isUtf8) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf8(); + _firstItem = false; + WriteSpacingUtf8(); - if (value) - WriteJsonValue(JsonConstants.TrueValue); + if (value) + WriteJsonValueUtf8(JsonConstants.TrueValue); + else + WriteJsonValueUtf8(JsonConstants.FalseValue); + } else - WriteJsonValue(JsonConstants.FalseValue); + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacingUtf16(); + + if (value) + WriteJsonValueUtf16(JsonConstants.TrueValue); + else + WriteJsonValueUtf16(JsonConstants.FalseValue); + } } /// @@ -524,12 +931,24 @@ public void WriteValue(bool value) /// The value to be written to JSON data. public void WriteValue(DateTime value) { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); + if (_isUtf8) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf8(); + _firstItem = false; + WriteSpacingUtf8(); + + WriteDateTime(value); + } + else + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacingUtf16(); - WriteDateTime(value); + WriteDateTime(value); + } } /// @@ -538,12 +957,24 @@ public void WriteValue(DateTime value) /// The value to be written to JSON data. public void WriteValue(DateTimeOffset value) { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); + if (_isUtf8) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf8(); + _firstItem = false; + WriteSpacingUtf8(); + + WriteDateTimeOffset(value); + } + else + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacingUtf16(); - WriteDateTimeOffset(value); + WriteDateTimeOffset(value); + } } /// @@ -552,12 +983,24 @@ public void WriteValue(DateTimeOffset value) /// The value to be written to JSON data. public void WriteValue(Guid value) { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); + if (_isUtf8) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf8(); + _firstItem = false; + WriteSpacingUtf8(); + + WriteGuid(value); + } + else + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacingUtf16(); - WriteGuid(value); + WriteGuid(value); + } } /// @@ -565,84 +1008,93 @@ public void WriteValue(Guid value) /// public void WriteNull() { - int bytesNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0) + JsonConstants.NullValue.Length; - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - if (_firstItem) - { - _firstItem = false; - } - else - { - byteBuffer[idx++] = JsonConstants.ListSeperator; - } - - if (_prettyPrint) + if (_isUtf8) { - int indent = _indent; - byteBuffer[idx++] = JsonConstants.CarriageReturn; - byteBuffer[idx++] = JsonConstants.LineFeed; - - while (indent-- >= 0) + int bytesNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0) + JsonConstants.NullValue.Length; + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + if (_firstItem) { - byteBuffer[idx++] = JsonConstants.Space; - byteBuffer[idx++] = JsonConstants.Space; + _firstItem = false; + } + else + { + byteBuffer[idx++] = JsonConstants.ListSeperator; } - } - - Debug.Assert(byteBuffer.Slice(idx).Length >= JsonConstants.NullValue.Length); - JsonConstants.NullValue.CopyTo(byteBuffer.Slice(idx)); - - _bufferWriter.Advance(bytesNeeded); - } - - private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded) - { - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + if (_prettyPrint) + { + int indent = _indent; + byteBuffer[idx++] = JsonConstants.CarriageReturn; + byteBuffer[idx++] = JsonConstants.LineFeed; + + while (indent-- >= 0) + { + byteBuffer[idx++] = JsonConstants.Space; + byteBuffer[idx++] = JsonConstants.Space; + } + } - byteBuffer[idx++] = JsonConstants.Quote; + Debug.Assert(byteBuffer.Slice(idx).Length >= JsonConstants.NullValue.Length); + JsonConstants.NullValue.CopyTo(byteBuffer.Slice(idx)); - OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); - Debug.Assert(consumed == nameSpanByte.Length); - if (status != OperationStatus.Done) - { - JsonThrowHelper.ThrowFormatException(); + _bufferWriter.Advance(bytesNeeded); } + else + { + int charsNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0); + int bytesNeeded = charsNeeded * 2 + JsonConstants.NullValueUtf16.Length; + Span byteBuffer = EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(byteBuffer); + int idx = 0; + if (_firstItem) + { + _firstItem = false; + } + else + { + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + } - idx += written; - - byteBuffer[idx++] = JsonConstants.Quote; - - byteBuffer[idx++] = JsonConstants.KeyValueSeperator; + if (_prettyPrint) + { + int indent = _indent; + charBuffer[idx++] = (char)JsonConstants.CarriageReturn; + charBuffer[idx++] = (char)JsonConstants.LineFeed; + + while (indent-- >= 0) + { + charBuffer[idx++] = (char)JsonConstants.Space; + charBuffer[idx++] = (char)JsonConstants.Space; + } + } - if (_prettyPrint) - byteBuffer[idx++] = JsonConstants.Space; + Debug.Assert(byteBuffer.Slice(idx * 2).Length >= JsonConstants.NullValueUtf16.Length); + JsonConstants.NullValueUtf16.CopyTo(byteBuffer.Slice(idx * 2)); - _bufferWriter.Advance(idx); - _firstItem = false; + _bufferWriter.Advance(bytesNeeded); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteControlUtf8(byte value) + private void WriteNumberUtf8(long value) { - MemoryMarshal.GetReference(EnsureBuffer(1)) = value; - _bufferWriter.Advance(1); + //TODO: Optimize, this is too slow + var buffer = _bufferWriter.GetSpan(); + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + buffer = EnsureBuffer(); + + _bufferWriter.Advance(written); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNumber(long value) + private void WriteNumberUtf16(long value) { //TODO: Optimize, this is too slow var buffer = _bufferWriter.GetSpan(); int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) buffer = EnsureBuffer(); _bufferWriter.Advance(written); @@ -693,7 +1145,7 @@ private void WriteGuid(Guid value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteJsonValue(ReadOnlySpan values) + private void WriteJsonValueUtf8(ReadOnlySpan values) { var buffer = _bufferWriter.GetSpan(); int written; @@ -703,6 +1155,32 @@ private void WriteJsonValue(ReadOnlySpan values) _bufferWriter.Advance(written); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteJsonValueUtf16(ReadOnlySpan values) + { + var buffer = _bufferWriter.GetSpan(); + int written; + while (!SymbolTable.InvariantUtf16.TryEncode(values, buffer, out int consumed, out written)) + buffer = EnsureBuffer(); + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteControlUtf8(byte value) + { + MemoryMarshal.GetReference(EnsureBuffer(1)) = value; + _bufferWriter.Advance(1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteControlUtf16(byte value) + { + var buffer = EnsureBuffer(2); + Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; + _bufferWriter.Advance(2); + } + // TODO: Once public methods are optimized, remove this. [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteItemSeperatorUtf8() @@ -712,6 +1190,15 @@ private void WriteItemSeperatorUtf8() WriteControlUtf8(JsonConstants.ListSeperator); } + // TODO: Once public methods are optimized, remove this. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteItemSeperatorUtf16() + { + if (_firstItem) return; + + WriteControlUtf16(JsonConstants.ListSeperator); + } + // TODO: Once public methods are optimized, remove this. [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteSpacingUtf8(bool newline = true) @@ -741,6 +1228,37 @@ private void WriteSpacingUtf8(bool newline = true) _bufferWriter.Advance(bytesNeeded); } + // TODO: Once public methods are optimized, remove this. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacingUtf16(bool newline = true) + { + if (!_prettyPrint) return; + + var indent = _indent; + var bytesNeeded = newline ? 2 : 0; + bytesNeeded += (indent + 1) * 2; + bytesNeeded *= sizeof(char); + + var buffer = EnsureBuffer(bytesNeeded); + var span = MemoryMarshal.Cast(buffer); + ref char utf16Bytes = ref MemoryMarshal.GetReference(span); + int idx = 0; + + if (newline) + { + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.CarriageReturn; + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.LineFeed; + } + + while (indent-- >= 0) + { + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; + } + + _bufferWriter.Advance(bytesNeeded); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private Span EnsureBuffer(int needed = 256) { @@ -768,13 +1286,13 @@ private int CalculateStartBytesNeeded(int numBytes) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateEndBytesNeeded(int numBytes) + private int CalculateEndBytesNeeded(int numBytes, int newLineLength) { int bytesNeeded = numBytes; if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLine.Length; // For the new line, \r\n or \n + int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -782,7 +1300,7 @@ private int CalculateEndBytesNeeded(int numBytes) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateValueBytesNeeded(int numBytes) + private int CalculateValueBytesNeeded(int numBytes, int newLineLength) { int bytesNeeded = 0; if (!_firstItem) @@ -790,7 +1308,7 @@ private int CalculateValueBytesNeeded(int numBytes) if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLine.Length; // For the new line, \r\n or \n + int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -806,7 +1324,7 @@ private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLine.Length; // For the new line, \r\n or \n + int bytesNeededForPrettyPrint = s_newLineUtf8.Length; // For the new line, \r\n or \n bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -829,7 +1347,7 @@ private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extr if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLine.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = s_newLineUtf8.Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -852,7 +1370,7 @@ private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLine.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = s_newLineUtf8.Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -875,7 +1393,7 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLine.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = s_newLineUtf8.Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -896,12 +1414,93 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS return bytesNeeded; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = s_newLineUtf16.Length; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * extraCharacterCount; + bytesNeeded += MemoryMarshal.AsBytes(span).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = s_newLineUtf16.Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * extraCharacterCount; + bytesNeeded += MemoryMarshal.AsBytes(span).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int numBytes) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = s_newLineUtf16.Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * 3; // quote {name} quote colon, hence 3 + bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlySpan valueSpan, int numBytes) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = s_newLineUtf16.Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * 5; //quote {name} quote colon quote {value} quote, hence 5 + bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; + bytesNeeded += MemoryMarshal.AsBytes(valueSpan).Length; + + return bytesNeeded; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddNewLineAndIndentation(Span buffer) { int offset = 0; // \r\n versus \n, depending on OS - if (s_newLine.Length == 2) + if (s_newLineUtf8.Length == 2) buffer[offset++] = JsonConstants.CarriageReturn; buffer[offset++] = JsonConstants.LineFeed; @@ -915,5 +1514,26 @@ private int AddNewLineAndIndentation(Span buffer) } return offset; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int AddNewLineAndIndentation(Span buffer) + { + int offset = 0; + // \r\n versus \n, depending on OS + if (s_newLineUtf16.Length == 2) + buffer[offset++] = (char)JsonConstants.CarriageReturn; + + buffer[offset++] = (char)JsonConstants.LineFeed; + + int indent = _indent; + + while (indent-- >= 0) + { + buffer[offset++] = (char)JsonConstants.Space; + buffer[offset++] = (char)JsonConstants.Space; + } + + return offset; + } } } diff --git a/tests/Benchmarks/System.IO.Pipelines/E2E.cs b/tests/Benchmarks/System.IO.Pipelines/E2E.cs index 063819a8f95..7ec919c25fb 100644 --- a/tests/Benchmarks/System.IO.Pipelines/E2E.cs +++ b/tests/Benchmarks/System.IO.Pipelines/E2E.cs @@ -57,7 +57,7 @@ public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) formatter.Append("\r\n\r\n"); // write body - var writer = new JsonWriterUtf8(formatter); + var writer = new JsonWriterUtf8(formatter, true); writer.WriteObjectStart(); writer.WriteAttribute("message", "Hello, World!"); writer.WriteObjectEnd(); diff --git a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs index eadeea6118c..673b8f39f50 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -102,7 +102,7 @@ private TextWriter GetWriter() private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter output, int[] data) { - var json = new JsonWriterUtf8(output, formatted); + var json = new JsonWriterUtf8(output, true, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -131,7 +131,7 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter private static void WriterSystemTextJsonBasicUtf16(bool formatted, ArrayFormatter output, int[] data) { - var json = new JsonWriterUtf16(output, formatted); + var json = new JsonWriterUtf8(output, false, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -201,7 +201,7 @@ private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayFormatter output) { - var json = new JsonWriterUtf8(output, formatted); + var json = new JsonWriterUtf8(output, true, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); @@ -210,7 +210,7 @@ private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayForm private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, ArrayFormatter output) { - var json = new JsonWriterUtf16(output, formatted); + var json = new JsonWriterUtf8(output, false, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); diff --git a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs index fb44b63e3eb..67e2c1b40c3 100644 --- a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs +++ b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs @@ -17,7 +17,7 @@ public class JsonWriterTests public void WriteJsonUtf8() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var json = new JsonWriterUtf8(formatter, prettyPrint: false); + var json = new JsonWriterUtf8(formatter, true, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -25,7 +25,7 @@ public void WriteJsonUtf8() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - json = new JsonWriterUtf8(formatter, prettyPrint: true); + json = new JsonWriterUtf8(formatter, true, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -37,7 +37,7 @@ public void WriteJsonUtf8() public void WriteJsonUtf16() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var json = new JsonWriterUtf16(formatter, prettyPrint: false); + var json = new JsonWriterUtf8(formatter, false, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -45,7 +45,7 @@ public void WriteJsonUtf16() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - json = new JsonWriterUtf16(formatter, prettyPrint: true); + json = new JsonWriterUtf8(formatter, false, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -78,30 +78,6 @@ static void Write(ref JsonWriterUtf8 json) json.WriteObjectEnd(); } - static void Write(ref JsonWriterUtf16 json) - { - json.WriteObjectStart(); - json.WriteAttribute("age", 30); - json.WriteAttribute("first", "John"); - json.WriteAttribute("last", "Smith"); - json.WriteArrayStart("phoneNumbers"); - json.WriteValue("425-000-1212"); - json.WriteValue("425-000-1213"); - json.WriteNull(); - json.WriteArrayEnd(); - json.WriteObjectStart("address"); - json.WriteAttribute("street", "1 Microsoft Way"); - json.WriteAttribute("city", "Redmond"); - json.WriteAttribute("zip", 98052); - json.WriteObjectEnd(); - json.WriteArrayStart("values"); - json.WriteValue(425121); - json.WriteValue(-425122); - json.WriteValue(425123); - json.WriteArrayEnd(); - json.WriteObjectEnd(); - } - [Theory] [InlineData(true)] [InlineData(false)] @@ -110,7 +86,7 @@ public void WriteHelloWorldJsonUtf16(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: false); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var jsonUtf16 = new JsonWriterUtf16(output, prettyPrint); + var jsonUtf16 = new JsonWriterUtf8(output, false, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("message", "Hello, World!"); @@ -130,7 +106,7 @@ public void WriteHelloWorldJsonUtf8(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: true); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var jsonUtf8 = new JsonWriterUtf8(output, prettyPrint); + var jsonUtf8 = new JsonWriterUtf8(output, true, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("message", "Hello, World!"); @@ -152,7 +128,7 @@ public void WriteBasicJsonUtf16(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: false, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var jsonUtf16 = new JsonWriterUtf16(output, prettyPrint); + var jsonUtf16 = new JsonWriterUtf8(output, false, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("age", 42); @@ -194,7 +170,7 @@ public void WriteBasicJsonUtf8(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: true, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var jsonUtf8 = new JsonWriterUtf8(output, prettyPrint); + var jsonUtf8 = new JsonWriterUtf8(output, true, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("age", 42);