From 597fd58cdda114180d35931a6f95a1d190557780 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Wed, 17 Apr 2019 23:57:50 -0700 Subject: [PATCH] Re-design Utf8JsonWriter as a regular class rather than a ref struct. (dotnet/corefx#36961) * Add an in-box array-backed IBufferWriter * Update Utf8JsonWriter ref and main writer file. * Fix JsonWriter WriteValue APIs. * Use Environment.NewLine static and invert some stream conditions. * Update JsonWriter properties and fix serializer build breaks. * Update JsonWriter unit tests. * Add xml comments, clean up, and improve test coverage. * Update JsonDocument and JsonSerializer to react to JsonWriter changes. * Normalize the reference assembly. * Do not escape/validate comments and update issue number. * Do not escape comments and validate they don't contain embedded delimiter * Remove dead code and update issue number in comments. * Throw InvalidOEx instead of ArgEx if IBW doesn't give requested memory. * Fix test build breaks for netfx. * Remove dead code and fix source packaging. * Address PR feedback. * Disable writing floats test on windows * 8 digit floats don't work well on older TFMs. Reduce to 7. Commit migrated from https://github.com/dotnet/corefx/commit/1f9b84a0804e868c7e0f37a3c10fbaf7c735ae14 --- .../Common/src/System/Buffers/ArrayBufferWriter.cs | 185 ++ src/libraries/System.Memory/ref/System.Memory.cs | 14 + .../System.Memory/src/Resources/Strings.resx | 3 + .../System.Memory/src/System.Memory.csproj | 3 + .../ArrayBufferWriterTests.Byte.cs | 367 +++ .../ArrayBufferWriterTests.Char.cs | 303 ++ .../ArrayBufferWriterTests.String.cs | 315 ++ .../System.Memory/tests/System.Memory.Tests.csproj | 3 + .../pkg/Microsoft.Bcl.Json.Sources.pkgproj | 2 +- .../ref/System.Text.Json.Manual.cs | 14 + .../System.Text.Json/ref/System.Text.Json.cs | 147 +- .../System.Text.Json/ref/System.Text.Json.csproj | 1 + .../System.Text.Json/src/Resources/Strings.resx | 9 +- .../System.Text.Json/src/System.Text.Json.csproj | 11 +- .../src/System/Text/Json/Document/JsonDocument.cs | 36 +- .../src/System/Text/Json/Document/JsonElement.cs | 12 +- .../Converters/JsonValueConverterBoolean.cs | 4 +- .../Converters/JsonValueConverterByte.cs | 4 +- .../Converters/JsonValueConverterChar.cs | 8 +- .../Converters/JsonValueConverterDateTime.cs | 4 +- .../Converters/JsonValueConverterDateTimeOffset.cs | 4 +- .../Converters/JsonValueConverterDecimal.cs | 4 +- .../Converters/JsonValueConverterDouble.cs | 4 +- .../Converters/JsonValueConverterEnum.cs | 4 +- .../Converters/JsonValueConverterInt16.cs | 4 +- .../Converters/JsonValueConverterInt32.cs | 4 +- .../Converters/JsonValueConverterInt64.cs | 4 +- .../Converters/JsonValueConverterSByte.cs | 4 +- .../Converters/JsonValueConverterSingle.cs | 4 +- .../Converters/JsonValueConverterString.cs | 4 +- .../Converters/JsonValueConverterUInt16.cs | 4 +- .../Converters/JsonValueConverterUInt32.cs | 4 +- .../Converters/JsonValueConverterUInt64.cs | 4 +- .../Text/Json/Serialization/JsonPropertyInfo.cs | 5 +- .../Serialization/JsonPropertyInfoNotNullable.cs | 12 +- .../Json/Serialization/JsonPropertyInfoNullable.cs | 12 +- .../JsonSerializer.Write.HandleEnumerable.cs | 12 +- .../JsonSerializer.Write.HandleObject.cs | 10 +- .../JsonSerializer.Write.HandleValue.cs | 24 - .../Serialization/JsonSerializer.Write.Helpers.cs | 23 +- .../Serialization/JsonSerializer.Write.Stream.cs | 13 +- .../Json/Serialization/JsonSerializer.Write.cs | 38 +- .../Serialization/Policies/JsonValueConverter.cs | 4 +- ...{ArrayBufferWriter.cs => PooledBufferWriter.cs} | 12 +- .../src/System/Text/Json/ThrowHelper.cs | 19 +- .../System/Text/Json/Writer/JsonWriterHelper.cs | 35 +- .../src/System/Text/Json/Writer/JsonWriterState.cs | 74 - .../Utf8JsonWriter.WriteProperties.DateTime.cs | 266 +- ...tf8JsonWriter.WriteProperties.DateTimeOffset.cs | 264 +- .../Utf8JsonWriter.WriteProperties.Decimal.cs | 231 +- .../Utf8JsonWriter.WriteProperties.Double.cs | 231 +- .../Writer/Utf8JsonWriter.WriteProperties.Float.cs | 237 +- ...f8JsonWriter.WriteProperties.FormattedNumber.cs | 116 +- .../Writer/Utf8JsonWriter.WriteProperties.Guid.cs | 250 +- .../Utf8JsonWriter.WriteProperties.Helpers.cs | 248 +- .../Utf8JsonWriter.WriteProperties.Literal.cs | 292 +- .../Utf8JsonWriter.WriteProperties.SignedNumber.cs | 255 +- .../Utf8JsonWriter.WriteProperties.String.cs | 566 ++-- ...tf8JsonWriter.WriteProperties.UnsignedNumber.cs | 255 +- .../Writer/Utf8JsonWriter.WriteValues.Comment.cs | 305 +- .../Writer/Utf8JsonWriter.WriteValues.DateTime.cs | 71 +- .../Utf8JsonWriter.WriteValues.DateTimeOffset.cs | 70 +- .../Writer/Utf8JsonWriter.WriteValues.Decimal.cs | 54 +- .../Writer/Utf8JsonWriter.WriteValues.Double.cs | 54 +- .../Writer/Utf8JsonWriter.WriteValues.Float.cs | 54 +- .../Utf8JsonWriter.WriteValues.FormattedNumber.cs | 60 +- .../Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs | 63 +- .../Writer/Utf8JsonWriter.WriteValues.Helpers.cs | 43 +- .../Writer/Utf8JsonWriter.WriteValues.Literal.cs | 55 +- .../Utf8JsonWriter.WriteValues.SignedNumber.cs | 54 +- .../Writer/Utf8JsonWriter.WriteValues.String.cs | 208 +- .../Utf8JsonWriter.WriteValues.UnsignedNumber.cs | 54 +- .../src/System/Text/Json/Writer/Utf8JsonWriter.cs | 759 +++-- .../System.Text.Json/tests/ArrayBufferWriter.cs | 86 - .../tests/FixedSizedBufferWriter.cs | 6 +- .../System.Text.Json/tests/JsonDocumentTests.cs | 12 +- .../tests/JsonElementWriteTests.cs | 140 +- .../tests/JsonWriterOptionsTests.cs | 56 + .../System.Text.Json/tests/JsonWriterStateTests.cs | 56 - .../System.Text.Json/tests/ResizableArray.cs | 43 - .../System.Text.Json/tests/Resources/Strings.resx | 3 + .../tests/System.Text.Json.Tests.csproj | 9 +- .../System.Text.Json/tests/Utf8JsonWriterTests.cs | 3036 +++++++++++--------- 83 files changed, 6412 insertions(+), 3940 deletions(-) create mode 100644 src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs create mode 100644 src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs create mode 100644 src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs create mode 100644 src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs create mode 100644 src/libraries/System.Text.Json/ref/System.Text.Json.Manual.cs delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs rename src/libraries/System.Text.Json/src/System/Text/Json/Serialization/{ArrayBufferWriter.cs => PooledBufferWriter.cs} (93%) delete mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs delete mode 100644 src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs create mode 100644 src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs delete mode 100644 src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs delete mode 100644 src/libraries/System.Text.Json/tests/ResizableArray.cs diff --git a/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs b/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs new file mode 100644 index 0000000..193eab8 --- /dev/null +++ b/src/libraries/Common/src/System/Buffers/ArrayBufferWriter.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Buffers +{ + /// + /// Represents a heap-based, array-backed output sink into which data can be written. + /// +#if USE_ABW_INTERNALLY + internal +#else + public +#endif + sealed class ArrayBufferWriter : IBufferWriter + { + private T[] _buffer; + private int _index; + + private const int MinimumBufferSize = 256; + + /// + /// Creates an instance of an , in which data can be written to, + /// with the default initial capacity. + /// + public ArrayBufferWriter() + { + _buffer = new T[MinimumBufferSize]; + _index = 0; + } + + /// + /// Creates an instance of an , in which data can be written to, + /// with an initial capacity specified. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// + /// Thrown when is not positive (i.e. less than or equal to 0). + /// + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + throw new ArgumentException(nameof(initialCapacity)); + + _buffer = new T[initialCapacity]; + _index = 0; + } + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, _index); + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, _index); + + /// + /// Returns the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => _index; + + /// + /// Returns the total amount of space within the underlying buffer. + /// + public int Capacity => _buffer.Length; + + /// + /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => _buffer.Length - _index; + + /// + /// Clears the data written to the underlying buffer. + /// + /// + /// You must clear the before trying to re-use it. + /// + public void Clear() + { + Debug.Assert(_buffer.Length >= _index); + _buffer.AsSpan(0, _index).Clear(); + _index = 0; + } + + /// + /// Notifies that amount of data was written to the output / + /// + /// + /// Thrown when is negative. + /// + /// + /// Thrown when attempting to advance past the end of the underlying buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public void Advance(int count) + { + if (count < 0) + throw new ArgumentException(nameof(count)); + + if (_index > _buffer.Length - count) + ThrowInvalidOperationException(_buffer.Length); + + _index += count; + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > _index); + return _buffer.AsMemory(_index); + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > _index); + return _buffer.AsSpan(_index); + } + + private void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + throw new ArgumentException(nameof(sizeHint)); + + if (sizeHint == 0) + { + sizeHint = MinimumBufferSize; + } + + if (sizeHint > FreeCapacity) + { + int growBy = Math.Max(sizeHint, _buffer.Length); + + int newSize = checked(_buffer.Length + growBy); + + Array.Resize(ref _buffer, newSize); + } + + Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); + } + + private static void ThrowInvalidOperationException(int capacity) + { + throw new InvalidOperationException(SR.Format(SR.BufferWriterAdvancedTooFar, capacity)); + } + } +} diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 387aa73..111a532 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -153,6 +153,20 @@ namespace System } namespace System.Buffers { + public sealed partial class ArrayBufferWriter : System.Buffers.IBufferWriter + { + public ArrayBufferWriter() { } + public ArrayBufferWriter(int initialCapacity) { } + public int Capacity { get { throw null; } } + public int FreeCapacity { get { throw null; } } + public int WrittenCount { get { throw null; } } + public System.ReadOnlyMemory WrittenMemory { get { throw null; } } + public System.ReadOnlySpan WrittenSpan { get { throw null; } } + public void Advance(int count) { } + public void Clear() { } + public System.Memory GetMemory(int sizeHint = 0) { throw null; } + public System.Span GetSpan(int sizeHint = 0) { throw null; } + } public static partial class BuffersExtensions { public static void CopyTo(this in System.Buffers.ReadOnlySequence source, System.Span destination) { } diff --git a/src/libraries/System.Memory/src/Resources/Strings.resx b/src/libraries/System.Memory/src/Resources/Strings.resx index 31ac00d..1ac9cac 100644 --- a/src/libraries/System.Memory/src/Resources/Strings.resx +++ b/src/libraries/System.Memory/src/Resources/Strings.resx @@ -143,4 +143,7 @@ Unexpected segment type. + + Cannot advance past the end of the buffer, which has a size of {0}. + diff --git a/src/libraries/System.Memory/src/System.Memory.csproj b/src/libraries/System.Memory/src/System.Memory.csproj index 685b2de..4afcbc5 100644 --- a/src/libraries/System.Memory/src/System.Memory.csproj +++ b/src/libraries/System.Memory/src/System.Memory.csproj @@ -33,6 +33,9 @@ Common\System\Collections\HashHelpers.cs + + Common\System\Buffers\ArrayBufferWriter.cs + diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs new file mode 100644 index 0000000..b6c730f --- /dev/null +++ b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Xunit; + +namespace System.Buffers.Tests +{ + public static partial class ArrayBufferWriterTests_Byte + { + [Fact] + public static void ArrayBufferWriter_Ctor() + { + { + var output = new ArrayBufferWriter(); + Assert.True(output.FreeCapacity > 0); + Assert.True(output.Capacity > 0); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + var output = new ArrayBufferWriter(200); + Assert.True(output.FreeCapacity >= 200); + Assert.True(output.Capacity >= 200); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + ArrayBufferWriter output = default; + Assert.Equal(null, output); + } + } + + [Fact] + public static void Invalid_Ctor() + { + Assert.Throws(() => new ArrayBufferWriter(0)); + Assert.Throws(() => new ArrayBufferWriter(-1)); + Assert.Throws(() => new ArrayBufferWriter(int.MaxValue)); + } + + [Fact] + public static void Clear() + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + WriteData(output, 2); + Assert.True(output.FreeCapacity < previousAvailable); + Assert.True(output.WrittenCount > 0); + Assert.False(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.False(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + output.Clear(); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + + [Fact] + public static void Advance() + { + { + var output = new ArrayBufferWriter(); + int capacity = output.Capacity; + Assert.Equal(capacity, output.FreeCapacity); + output.Advance(output.FreeCapacity); + Assert.Equal(capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + } + + { + var output = new ArrayBufferWriter(); + output.Advance(output.Capacity); + Assert.Equal(output.Capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + int previousCapacity = output.Capacity; + Span _ = output.GetSpan(); + Assert.True(output.Capacity > previousCapacity); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(10); + Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.False(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + } + + [Fact] + public static void AdvanceZero() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Equal(2, output.WrittenCount); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(0); + Assert.Equal(2, output.WrittenCount); + Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + + [Fact] + public static void InvalidAdvance() + { + { + var output = new ArrayBufferWriter(); + Assert.Throws(() => output.Advance(-1)); + Assert.Throws(() => output.Advance(output.Capacity + 1)); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + Assert.Throws(() => output.Advance(output.FreeCapacity + 1)); + } + } + + public static bool IsX64 { get; } = IntPtr.Size >= 8; + + [ConditionalFact(nameof(IsX64))] + [OuterLoop] + public static void InvalidAdvance_Large() + { + try + { + { + var output = new ArrayBufferWriter(2_000_000_000); + WriteData(output, 1_000); + Assert.Throws(() => output.Advance(int.MaxValue)); + Assert.Throws(() => output.Advance(2_000_000_000 - 1_000 + 1)); + } + } + catch (OutOfMemoryException) { } + } + + [Fact] + public static void GetMemoryAndSpan() + { + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Span span = output.GetSpan(); + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length > 0); + Assert.True(memorySpan.Length > 0); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory writtenSoFarMemory = output.WrittenMemory; + ReadOnlySpan writtenSoFar = output.WrittenSpan; + Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar)); + int previousAvailable = output.FreeCapacity; + Span span = output.GetSpan(500); + Assert.True(span.Length >= 500); + Assert.True(output.FreeCapacity >= 500); + Assert.True(output.FreeCapacity > previousAvailable); + + Assert.Equal(writtenSoFar.Length, output.WrittenCount); + Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount))); + + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length >= 500); + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + + memory = output.GetMemory(500); + memorySpan = memory.Span; + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < memorySpan.Length; i++) + { + Assert.Equal(default, memorySpan[i]); + } + } + } + + [Fact] + public static void GetSpanShouldAtleastDoubleWhenGrowing() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + _ = output.GetSpan(previousAvailable + 1); + Assert.True(output.FreeCapacity >= previousAvailable * 2); + } + + [Fact] + public static void GetSpanOnlyGrowsAboveThreshold() + { + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + previousAvailable = output.FreeCapacity; + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + } + + [Fact] + public static void InvalidGetMemoryAndSpan() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Throws(() => output.GetSpan(-1)); + Assert.Throws(() => output.GetMemory(-1)); + } + + [Fact] + public static void MultipleCallsToGetSpan() + { + var output = new ArrayBufferWriter(300); + int previousAvailable = output.FreeCapacity; + Assert.True(previousAvailable >= 300); + Assert.True(output.Capacity >= 300); + Assert.Equal(previousAvailable, output.Capacity); + Span span = output.GetSpan(); + Assert.True(span.Length >= previousAvailable); + Assert.True(span.Length >= 256); + Span newSpan = output.GetSpan(); + Assert.Equal(span.Length, newSpan.Length); + + unsafe + { + void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan)); + Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan); + } + + Assert.Equal(span.Length, output.GetSpan().Length); + } + + [Fact] + public static void WriteAndCopyToStream() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + using (var memStream = new MemoryStream(100)) + { + Assert.Equal(100, output.WrittenCount); + + ReadOnlySpan outputSpan = output.WrittenMemory.ToArray(); + + ReadOnlyMemory transientMemory = output.WrittenMemory; + ReadOnlySpan transientSpan = output.WrittenSpan; + + Assert.True(transientSpan.SequenceEqual(transientMemory.Span)); + + Assert.True(transientSpan[0] != 0); + + memStream.Write(transientSpan.ToArray(), 0, transientSpan.Length); + output.Clear(); + + Assert.True(transientSpan[0] == 0); + Assert.True(transientMemory.Span[0] == 0); + + Assert.Equal(0, output.WrittenCount); + byte[] streamOutput = memStream.ToArray(); + + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + + Assert.Equal(outputSpan.Length, streamOutput.Length); + Assert.True(outputSpan.SequenceEqual(streamOutput)); + } + } + + [Fact] + public static async Task WriteAndCopyToStreamAsync() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + using (var memStream = new MemoryStream(100)) + { + Assert.Equal(100, output.WrittenCount); + + ReadOnlyMemory outputMemory = output.WrittenMemory.ToArray(); + + ReadOnlyMemory transient = output.WrittenMemory; + + Assert.True(transient.Span[0] != 0); + + await memStream.WriteAsync(transient.ToArray(), 0, transient.Length); + output.Clear(); + + Assert.True(transient.Span[0] == 0); + + Assert.Equal(0, output.WrittenCount); + byte[] streamOutput = memStream.ToArray(); + + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenMemory.Span)); + + Assert.Equal(outputMemory.Length, streamOutput.Length); + Assert.True(outputMemory.Span.SequenceEqual(streamOutput)); + } + } + + private static void WriteData(IBufferWriter bufferWriter, int numBytes) + { + Span outputSpan = bufferWriter.GetSpan(numBytes); + Debug.Assert(outputSpan.Length >= numBytes); + var random = new Random(42); + + var data = new byte[numBytes]; + random.NextBytes(data); + data.CopyTo(outputSpan); + + bufferWriter.Advance(numBytes); + } + } +} diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs new file mode 100644 index 0000000..8b5cf11 --- /dev/null +++ b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs @@ -0,0 +1,303 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; + +namespace System.Buffers.Tests +{ + public static partial class ArrayBufferWriterTests_Char + { + [Fact] + public static void ArrayBufferWriter_Ctor() + { + { + var output = new ArrayBufferWriter(); + Assert.True(output.FreeCapacity > 0); + Assert.True(output.Capacity > 0); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + var output = new ArrayBufferWriter(200); + Assert.True(output.FreeCapacity >= 200); + Assert.True(output.Capacity >= 200); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + ArrayBufferWriter output = default; + Assert.Equal(null, output); + } + } + + [Fact] + public static void Invalid_Ctor() + { + Assert.Throws(() => new ArrayBufferWriter(0)); + Assert.Throws(() => new ArrayBufferWriter(-1)); + Assert.Throws(() => new ArrayBufferWriter(int.MaxValue)); + } + + [Fact] + public static void Clear() + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + WriteData(output, 2); + Assert.True(output.FreeCapacity < previousAvailable); + Assert.True(output.WrittenCount > 0); + Assert.False(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.False(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + output.Clear(); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + + [Fact] + public static void Advance() + { + { + var output = new ArrayBufferWriter(); + int capacity = output.Capacity; + Assert.Equal(capacity, output.FreeCapacity); + output.Advance(output.FreeCapacity); + Assert.Equal(capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + } + + { + var output = new ArrayBufferWriter(); + output.Advance(output.Capacity); + Assert.Equal(output.Capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + int previousCapacity = output.Capacity; + _ = output.GetSpan(); + Assert.True(output.Capacity > previousCapacity); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(10); + Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.False(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + } + + [Fact] + public static void AdvanceZero() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Equal(2, output.WrittenCount); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(0); + Assert.Equal(2, output.WrittenCount); + Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + + [Fact] + public static void InvalidAdvance() + { + { + var output = new ArrayBufferWriter(); + Assert.Throws(() => output.Advance(-1)); + Assert.Throws(() => output.Advance(output.Capacity + 1)); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + Assert.Throws(() => output.Advance(output.FreeCapacity + 1)); + } + } + + public static bool IsX64 { get; } = IntPtr.Size >= 8; + + [ConditionalFact(nameof(IsX64))] + [OuterLoop] + public static void InvalidAdvance_Large() + { + try + { + { + var output = new ArrayBufferWriter(2_000_000_000); + WriteData(output, 1_000); + Assert.Throws(() => output.Advance(int.MaxValue)); + Assert.Throws(() => output.Advance(2_000_000_000 - 1_000 + 1)); + } + } + catch (OutOfMemoryException) { } + } + + [Fact] + public static void GetMemoryAndSpan() + { + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Span span = output.GetSpan(); + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length > 0); + Assert.True(memorySpan.Length > 0); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory writtenSoFarMemory = output.WrittenMemory; + ReadOnlySpan writtenSoFar = output.WrittenSpan; + Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar)); + int previousAvailable = output.FreeCapacity; + Span span = output.GetSpan(500); + Assert.True(span.Length >= 500); + Assert.True(output.FreeCapacity >= 500); + Assert.True(output.FreeCapacity > previousAvailable); + + Assert.Equal(writtenSoFar.Length, output.WrittenCount); + Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount))); + + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length >= 500); + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + + memory = output.GetMemory(500); + memorySpan = memory.Span; + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < memorySpan.Length; i++) + { + Assert.Equal(default, memorySpan[i]); + } + } + } + + [Fact] + public static void GetSpanShouldAtleastDoubleWhenGrowing() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + _ = output.GetSpan(previousAvailable + 1); + Assert.True(output.FreeCapacity >= previousAvailable * 2); + } + + [Fact] + public static void GetSpanOnlyGrowsAboveThreshold() + { + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + previousAvailable = output.FreeCapacity; + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + } + + [Fact] + public static void InvalidGetMemoryAndSpan() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Throws(() => output.GetSpan(-1)); + Assert.Throws(() => output.GetMemory(-1)); + } + + [Fact] + public static void MultipleCallsToGetSpan() + { + var output = new ArrayBufferWriter(300); + int previousAvailable = output.FreeCapacity; + Assert.True(previousAvailable >= 300); + Assert.True(output.Capacity >= 300); + Assert.Equal(previousAvailable, output.Capacity); + Span span = output.GetSpan(); + Assert.True(span.Length >= previousAvailable); + Assert.True(span.Length >= 256); + Span newSpan = output.GetSpan(); + Assert.Equal(span.Length, newSpan.Length); + + unsafe + { + void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan)); + Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan); + } + + Assert.Equal(span.Length, output.GetSpan().Length); + } + + private static void WriteData(IBufferWriter bufferWriter, int numChars) + { + Span outputSpan = bufferWriter.GetSpan(numChars); + Debug.Assert(outputSpan.Length >= numChars); + var random = new Random(42); + + var data = new char[numChars]; + + for (int i = 0; i < numChars; i++) + { + data[i] = (char)random.Next(0, char.MaxValue); + } + + data.CopyTo(outputSpan); + + bufferWriter.Advance(numChars); + } + } +} diff --git a/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs new file mode 100644 index 0000000..cfb16ab --- /dev/null +++ b/src/libraries/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs @@ -0,0 +1,315 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace System.Buffers.Tests +{ + public static partial class ArrayBufferWriterTests_String + { + [Fact] + public static void ArrayBufferWriter_Ctor() + { + { + var output = new ArrayBufferWriter(); + Assert.True(output.FreeCapacity > 0); + Assert.True(output.Capacity > 0); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + var output = new ArrayBufferWriter(200); + Assert.True(output.FreeCapacity >= 200); + Assert.True(output.Capacity >= 200); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + ArrayBufferWriter output = default; + Assert.Equal(null, output); + } + } + + [Fact] + public static void Invalid_Ctor() + { + Assert.Throws(() => new ArrayBufferWriter(0)); + Assert.Throws(() => new ArrayBufferWriter(-1)); + Assert.Throws(() => new ArrayBufferWriter(int.MaxValue)); + } + + [Fact] + public static void Clear() + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + WriteData(output, 2); + Assert.True(output.FreeCapacity < previousAvailable); + Assert.True(output.WrittenCount > 0); + Assert.False(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.False(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + output.Clear(); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + + [Fact] + public static void Advance() + { + { + var output = new ArrayBufferWriter(); + int capacity = output.Capacity; + Assert.Equal(capacity, output.FreeCapacity); + output.Advance(output.FreeCapacity); + Assert.Equal(capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + } + + { + var output = new ArrayBufferWriter(); + output.Advance(output.Capacity); + Assert.Equal(output.Capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + int previousCapacity = output.Capacity; + _ = output.GetSpan(); + Assert.True(output.Capacity > previousCapacity); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(10); + Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.False(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + } + + [Fact] + public static void AdvanceZero() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Equal(2, output.WrittenCount); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(0); + Assert.Equal(2, output.WrittenCount); + Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + + [Fact] + public static void InvalidAdvance() + { + { + var output = new ArrayBufferWriter(); + Assert.Throws(() => output.Advance(-1)); + Assert.Throws(() => output.Advance(output.Capacity + 1)); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + Assert.Throws(() => output.Advance(output.FreeCapacity + 1)); + } + } + + public static bool IsX64 { get; } = IntPtr.Size >= 8; + + [ConditionalFact(nameof(IsX64))] + [OuterLoop] + public static void InvalidAdvance_Large() + { + try + { + { + var output = new ArrayBufferWriter(2_000_000_000); + WriteData(output, 1_000); + Assert.Throws(() => output.Advance(int.MaxValue)); + Assert.Throws(() => output.Advance(2_000_000_000 - 1_000 + 1)); + } + } + catch (OutOfMemoryException) { } + } + + [Fact] + public static void GetMemoryAndSpan() + { + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Span span = output.GetSpan(); + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length > 0); + Assert.True(memorySpan.Length > 0); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory writtenSoFarMemory = output.WrittenMemory; + ReadOnlySpan writtenSoFar = output.WrittenSpan; + Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar)); + int previousAvailable = output.FreeCapacity; + Span span = output.GetSpan(500); + Assert.True(span.Length >= 500); + Assert.True(output.FreeCapacity >= 500); + Assert.True(output.FreeCapacity > previousAvailable); + + Assert.Equal(writtenSoFar.Length, output.WrittenCount); + Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount))); + + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length >= 500); + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + + memory = output.GetMemory(500); + memorySpan = memory.Span; + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < memorySpan.Length; i++) + { + Assert.Equal(default, memorySpan[i]); + } + } + } + + [Fact] + public static void GetSpanShouldAtleastDoubleWhenGrowing() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + _ = output.GetSpan(previousAvailable + 1); + Assert.True(output.FreeCapacity >= previousAvailable * 2); + } + + [Fact] + public static void GetSpanOnlyGrowsAboveThreshold() + { + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + previousAvailable = output.FreeCapacity; + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + } + + [Fact] + public static void InvalidGetMemoryAndSpan() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Throws(() => output.GetSpan(-1)); + Assert.Throws(() => output.GetMemory(-1)); + } + + [Fact] + public static void MultipleCallsToGetSpan() + { + var output = new ArrayBufferWriter(300); + int previousAvailable = output.FreeCapacity; + Assert.True(previousAvailable >= 300); + Assert.True(output.Capacity >= 300); + Assert.Equal(previousAvailable, output.Capacity); + Span span = output.GetSpan(); + Assert.True(span.Length >= previousAvailable); + Assert.True(span.Length >= 256); + Span newSpan = output.GetSpan(); + Assert.Equal(span.Length, newSpan.Length); + + unsafe + { + void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan)); + Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan); + } + + Assert.Equal(span.Length, output.GetSpan().Length); + } + + private static void WriteData(IBufferWriter bufferWriter, int numStrings) + { + Span outputSpan = bufferWriter.GetSpan(numStrings); + Debug.Assert(outputSpan.Length >= numStrings); + var random = new Random(42); + + var data = new string[numStrings]; + + for (int i = 0; i < numStrings; i++) + { + int length = random.Next(5, 10); + data[i] = GetRandomString(random, length, 32, 127); + } + + data.CopyTo(outputSpan); + + bufferWriter.Advance(numStrings); + } + + private static string GetRandomString(Random r, int length, int minCodePoint, int maxCodePoint) + { + StringBuilder sb = new StringBuilder(length); + while (length-- != 0) + { + sb.Append((char)r.Next(minCodePoint, maxCodePoint)); + } + return sb.ToString(); + } + } +} diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index 92f23d7..ac76298 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -9,6 +9,9 @@ + + + diff --git a/src/libraries/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj b/src/libraries/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj index e2ba61f..40d70c6 100644 --- a/src/libraries/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj +++ b/src/libraries/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj @@ -14,7 +14,7 @@ the Common SR.cs into SourcePackageFiles. --> - <_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" /> + <_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" AdditionalProperties="TargetGroup=netstandard"/> utf8PropertyName, ref System.Text.Json.Utf8JsonWriter writer) { } - public void WriteAsProperty(System.ReadOnlySpan propertyName, ref System.Text.Json.Utf8JsonWriter writer) { } - public void WriteAsValue(ref System.Text.Json.Utf8JsonWriter writer) { } + public void WriteAsProperty(System.ReadOnlySpan utf8PropertyName, System.Text.Json.Utf8JsonWriter writer) { } + public void WriteAsProperty(System.ReadOnlySpan propertyName, System.Text.Json.Utf8JsonWriter writer) { } + public void WriteAsValue(System.Text.Json.Utf8JsonWriter writer) { } public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable { private object _dummy; @@ -163,15 +163,6 @@ namespace System.Text.Json public bool Indented { get { throw null; } set { } } public bool SkipValidation { get { throw null; } set { } } } - public partial struct JsonWriterState - { - private object _dummy; - private int _dummyPrimitive; - public JsonWriterState(System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { throw null; } - public long BytesCommitted { get { throw null; } } - public long BytesWritten { get { throw null; } } - public System.Text.Json.JsonWriterOptions Options { get { throw null; } } - } public ref partial struct Utf8JsonReader { private object _dummy; @@ -216,56 +207,60 @@ namespace System.Text.Json [System.CLSCompliantAttribute(false)] public bool TryGetUInt64(out ulong value) { throw null; } } - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter : System.IDisposable { - private object _dummy; - private int _dummyPrimitive; - public Utf8JsonWriter(System.Buffers.IBufferWriter bufferWriter, System.Text.Json.JsonWriterState state = default(System.Text.Json.JsonWriterState)) { throw null; } + public Utf8JsonWriter(System.Buffers.IBufferWriter bufferWriter, System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { } + public Utf8JsonWriter(System.IO.Stream utf8Json, System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { } public long BytesCommitted { get { throw null; } } - public long BytesWritten { get { throw null; } } + public int BytesPending { get { throw null; } } public int CurrentDepth { get { throw null; } } - public void Flush(bool isFinalBlock = true) { } - public System.Text.Json.JsonWriterState GetCurrentState() { throw null; } - public void WriteBoolean(System.ReadOnlySpan utf8PropertyName, bool value, bool escape = true) { } - public void WriteBoolean(System.ReadOnlySpan propertyName, bool value, bool escape = true) { } - public void WriteBoolean(string propertyName, bool value, bool escape = true) { } + public System.Text.Json.JsonWriterOptions Options { get { throw null; } } + public void Dispose() { } + public void Flush() { } + public System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public void Reset() { } + public void Reset(System.Buffers.IBufferWriter bufferWriter) { } + public void Reset(System.IO.Stream utf8Json) { } + public void WriteBoolean(System.ReadOnlySpan utf8PropertyName, bool value) { } + public void WriteBoolean(System.ReadOnlySpan propertyName, bool value) { } + public void WriteBoolean(string propertyName, bool value) { } public void WriteBooleanValue(bool value) { } - public void WriteCommentValue(System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteCommentValue(System.ReadOnlySpan value, bool escape = true) { } - public void WriteCommentValue(string value, bool escape = true) { } + public void WriteCommentValue(System.ReadOnlySpan utf8Value) { } + public void WriteCommentValue(System.ReadOnlySpan value) { } + public void WriteCommentValue(string value) { } public void WriteEndArray() { } public void WriteEndObject() { } - public void WriteNull(System.ReadOnlySpan utf8PropertyName, bool escape = true) { } - public void WriteNull(System.ReadOnlySpan propertyName, bool escape = true) { } - public void WriteNull(string propertyName, bool escape = true) { } + public void WriteNull(System.ReadOnlySpan utf8PropertyName) { } + public void WriteNull(System.ReadOnlySpan propertyName) { } + public void WriteNull(string propertyName) { } public void WriteNullValue() { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, decimal value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, double value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, int value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, long value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, float value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, decimal value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, double value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, int value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, long value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, float value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, uint value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, uint value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, ulong value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, decimal value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, double value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, int value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, long value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, float value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, ulong value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, decimal value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, double value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, int value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, long value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, float value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan propertyName, uint value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan propertyName, uint value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan propertyName, ulong value, bool escape = true) { } - public void WriteNumber(string propertyName, decimal value, bool escape = true) { } - public void WriteNumber(string propertyName, double value, bool escape = true) { } - public void WriteNumber(string propertyName, int value, bool escape = true) { } - public void WriteNumber(string propertyName, long value, bool escape = true) { } - public void WriteNumber(string propertyName, float value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan propertyName, ulong value) { } + public void WriteNumber(string propertyName, decimal value) { } + public void WriteNumber(string propertyName, double value) { } + public void WriteNumber(string propertyName, int value) { } + public void WriteNumber(string propertyName, long value) { } + public void WriteNumber(string propertyName, float value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(string propertyName, uint value, bool escape = true) { } + public void WriteNumber(string propertyName, uint value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(string propertyName, ulong value, bool escape = true) { } + public void WriteNumber(string propertyName, ulong value) { } public void WriteNumberValue(decimal value) { } public void WriteNumberValue(double value) { } public void WriteNumberValue(int value) { } @@ -276,37 +271,37 @@ namespace System.Text.Json [System.CLSCompliantAttribute(false)] public void WriteNumberValue(ulong value) { } public void WriteStartArray() { } - public void WriteStartArray(System.ReadOnlySpan utf8PropertyName, bool escape = true) { } - public void WriteStartArray(System.ReadOnlySpan propertyName, bool escape = true) { } - public void WriteStartArray(string propertyName, bool escape = true) { } + public void WriteStartArray(System.ReadOnlySpan utf8PropertyName) { } + public void WriteStartArray(System.ReadOnlySpan propertyName) { } + public void WriteStartArray(string propertyName) { } public void WriteStartObject() { } - public void WriteStartObject(System.ReadOnlySpan utf8PropertyName, bool escape = true) { } - public void WriteStartObject(System.ReadOnlySpan propertyName, bool escape = true) { } - public void WriteStartObject(string propertyName, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTime value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTimeOffset value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.Guid value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, string value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.DateTime value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.DateTimeOffset value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.Guid value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, string value, bool escape = true) { } - public void WriteString(string propertyName, System.DateTime value, bool escape = true) { } - public void WriteString(string propertyName, System.DateTimeOffset value, bool escape = true) { } - public void WriteString(string propertyName, System.Guid value, bool escape = true) { } - public void WriteString(string propertyName, System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteString(string propertyName, System.ReadOnlySpan value, bool escape = true) { } - public void WriteString(string propertyName, string value, bool escape = true) { } + public void WriteStartObject(System.ReadOnlySpan utf8PropertyName) { } + public void WriteStartObject(System.ReadOnlySpan propertyName) { } + public void WriteStartObject(string propertyName) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTime value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTimeOffset value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.Guid value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan utf8Value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, string value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.DateTime value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.DateTimeOffset value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.Guid value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan utf8Value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan value) { } + public void WriteString(System.ReadOnlySpan propertyName, string value) { } + public void WriteString(string propertyName, System.DateTime value) { } + public void WriteString(string propertyName, System.DateTimeOffset value) { } + public void WriteString(string propertyName, System.Guid value) { } + public void WriteString(string propertyName, System.ReadOnlySpan utf8Value) { } + public void WriteString(string propertyName, System.ReadOnlySpan value) { } + public void WriteString(string propertyName, string value) { } public void WriteStringValue(System.DateTime value) { } public void WriteStringValue(System.DateTimeOffset value) { } public void WriteStringValue(System.Guid value) { } - public void WriteStringValue(System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteStringValue(System.ReadOnlySpan value, bool escape = true) { } - public void WriteStringValue(string value, bool escape = true) { } + public void WriteStringValue(System.ReadOnlySpan utf8Value) { } + public void WriteStringValue(System.ReadOnlySpan value) { } + public void WriteStringValue(string value) { } } } namespace System.Text.Json.Serialization diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj index 002d0f8..0914d09 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj @@ -7,6 +7,7 @@ + diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 0cd15e0..d2eff55 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -204,9 +204,6 @@ The 'IBufferWriter' could not provide an output buffer that is large enough to continue writing. - - The 'IBufferWriter' could not provide an output buffer that is large enough to continue writing. Need at least {0} bytes. - '{0}' is invalid after a value. Expected either ',', '}}', or ']'. @@ -324,6 +321,12 @@ Serializer options cannot be changed once serialization or deserialization has occurred. + + Stream was not writable. + + + Cannot write a comment value which contains the end of comment delimiter. + The property '{0}.{1}' has the same name as a previous property based on naming or casing policies. diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index b8d9ac1..c3c7e37 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -9,6 +9,7 @@ $(DefineConstants);BUILDING_INBOX_LIBRARY + $(DefineConstants);USE_ABW_INTERNALLY @@ -41,7 +42,6 @@ - @@ -93,9 +93,9 @@ - + @@ -108,7 +108,6 @@ - @@ -137,6 +136,12 @@ + + + + Common\System\Buffers\ArrayBufferWriter.cs + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs index a77f5a8..65766b8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs @@ -603,7 +603,7 @@ namespace System.Text.Json /// internal void WriteElementTo( int index, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ReadOnlySpan propertyName) { CheckNotDisposed(); @@ -614,14 +614,14 @@ namespace System.Text.Json { case JsonTokenType.StartObject: writer.WriteStartObject(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.StartArray: writer.WriteStartArray(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.String: - WriteString(propertyName, row, ref writer); + WriteString(propertyName, row, writer); return; case JsonTokenType.True: writer.WriteBoolean(propertyName, value: true); @@ -647,7 +647,7 @@ namespace System.Text.Json /// internal void WriteElementTo( int index, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ReadOnlySpan propertyName) { CheckNotDisposed(); @@ -658,14 +658,14 @@ namespace System.Text.Json { case JsonTokenType.StartObject: writer.WriteStartObject(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.StartArray: writer.WriteStartArray(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.String: - WriteString(propertyName, row, ref writer); + WriteString(propertyName, row, writer); return; case JsonTokenType.True: writer.WriteBoolean(propertyName, value: true); @@ -691,7 +691,7 @@ namespace System.Text.Json /// internal void WriteElementTo( int index, - ref Utf8JsonWriter writer) + Utf8JsonWriter writer) { CheckNotDisposed(); @@ -701,14 +701,14 @@ namespace System.Text.Json { case JsonTokenType.StartObject: writer.WriteStartObject(); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.StartArray: writer.WriteStartArray(); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.String: - WriteString(row, ref writer); + WriteString(row, writer); return; case JsonTokenType.Number: writer.WriteNumberValue(_utf8Json.Slice(row.Location, row.SizeOrLength).Span); @@ -727,7 +727,7 @@ namespace System.Text.Json Debug.Fail($"Unexpected encounter with JsonTokenType {row.TokenType}"); } - private void WriteComplexElement(int index, ref Utf8JsonWriter writer) + private void WriteComplexElement(int index, Utf8JsonWriter writer) { int endIndex = GetEndIndex(index, true); @@ -739,7 +739,7 @@ namespace System.Text.Json switch (row.TokenType) { case JsonTokenType.String: - WriteString(row, ref writer); + WriteString(row, writer); continue; case JsonTokenType.Number: writer.WriteNumberValue(_utf8Json.Slice(row.Location, row.SizeOrLength).Span); @@ -778,7 +778,7 @@ namespace System.Text.Json switch (propertyValue.TokenType) { case JsonTokenType.String: - WriteString(propertyName, propertyValue, ref writer); + WriteString(propertyName, propertyValue, writer); continue; case JsonTokenType.Number: writer.WriteNumber( @@ -844,7 +844,7 @@ namespace System.Text.Json } } - private void WriteString(ReadOnlySpan propertyName, in DbRow row, ref Utf8JsonWriter writer) + private void WriteString(ReadOnlySpan propertyName, in DbRow row, Utf8JsonWriter writer) { ArraySegment rented = default; @@ -860,7 +860,7 @@ namespace System.Text.Json } } - private void WriteString(ReadOnlySpan propertyName, in DbRow row, ref Utf8JsonWriter writer) + private void WriteString(ReadOnlySpan propertyName, in DbRow row, Utf8JsonWriter writer) { ArraySegment rented = default; @@ -877,7 +877,7 @@ namespace System.Text.Json } } - private void WriteString(in DbRow row, ref Utf8JsonWriter writer) + private void WriteString(in DbRow row, Utf8JsonWriter writer) { ArraySegment rented = default; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index 03d2698..1ff367a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -940,11 +940,11 @@ namespace System.Text.Json /// /// The parent has been disposed. /// - public void WriteAsProperty(ReadOnlySpan propertyName, ref Utf8JsonWriter writer) + public void WriteAsProperty(ReadOnlySpan propertyName, Utf8JsonWriter writer) { CheckValidInstance(); - _parent.WriteElementTo(_idx, ref writer, propertyName); + _parent.WriteElementTo(_idx, writer, propertyName); } /// @@ -960,11 +960,11 @@ namespace System.Text.Json /// /// The parent has been disposed. /// - public void WriteAsProperty(ReadOnlySpan utf8PropertyName, ref Utf8JsonWriter writer) + public void WriteAsProperty(ReadOnlySpan utf8PropertyName, Utf8JsonWriter writer) { CheckValidInstance(); - _parent.WriteElementTo(_idx, ref writer, utf8PropertyName); + _parent.WriteElementTo(_idx, writer, utf8PropertyName); } /// @@ -977,11 +977,11 @@ namespace System.Text.Json /// /// The parent has been disposed. /// - public void WriteAsValue(ref Utf8JsonWriter writer) + public void WriteAsValue(Utf8JsonWriter writer) { CheckValidInstance(); - _parent.WriteElementTo(_idx, ref writer); + _parent.WriteElementTo(_idx, writer); } /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs index 918c1e4..9526f67 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs @@ -20,12 +20,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(bool value, ref Utf8JsonWriter writer) + public override void Write(bool value, Utf8JsonWriter writer) { writer.WriteBooleanValue(value); } - public override void Write(Span escapedPropertyName, bool value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, bool value, Utf8JsonWriter writer) { writer.WriteBoolean(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs index ce2a95f..174a87b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs @@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(byte value, ref Utf8JsonWriter writer) + public override void Write(byte value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, byte value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, byte value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs index 30aa4e7..1072743 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs @@ -21,20 +21,20 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(char value, ref Utf8JsonWriter writer) + public override void Write(char value, Utf8JsonWriter writer) { #if BUILDING_INBOX_LIBRARY - Span temp = MemoryMarshal.CreateSpan(ref value, 1); + Span temp = MemoryMarshal.CreateSpan(ref value, 1); writer.WriteStringValue(temp); #else writer.WriteStringValue(value.ToString()); #endif } - public override void Write(Span escapedPropertyName, char value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, char value, Utf8JsonWriter writer) { #if BUILDING_INBOX_LIBRARY - Span temp = MemoryMarshal.CreateSpan(ref value, 1); + Span temp = MemoryMarshal.CreateSpan(ref value, 1); writer.WriteString(escapedPropertyName, temp); #else writer.WriteString(escapedPropertyName, value.ToString()); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs index 7b49f8e..f15470c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs @@ -13,12 +13,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetDateTime(out value); } - public override void Write(DateTime value, ref Utf8JsonWriter writer) + public override void Write(DateTime value, Utf8JsonWriter writer) { writer.WriteStringValue(value); } - public override void Write(Span escapedPropertyName, DateTime value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, DateTime value, Utf8JsonWriter writer) { writer.WriteString(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs index a19b082..0f9d6d3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs @@ -13,12 +13,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetDateTimeOffset(out value); } - public override void Write(DateTimeOffset value, ref Utf8JsonWriter writer) + public override void Write(DateTimeOffset value, Utf8JsonWriter writer) { writer.WriteStringValue(value); } - public override void Write(Span escapedPropertyName, DateTimeOffset value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, DateTimeOffset value, Utf8JsonWriter writer) { writer.WriteString(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs index b5dcb88..ed90587 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs @@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetDecimal(out value); } - public override void Write(decimal value, ref Utf8JsonWriter writer) + public override void Write(decimal value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, decimal value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, decimal value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs index 91c7896..5d23696 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs @@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetDouble(out value); } - public override void Write(double value, ref Utf8JsonWriter writer) + public override void Write(double value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, double value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, double value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs index 10f08c1..b99dcc9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs @@ -44,7 +44,7 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(TValue value, ref Utf8JsonWriter writer) + public override void Write(TValue value, Utf8JsonWriter writer) { if (TreatAsString) { @@ -64,7 +64,7 @@ namespace System.Text.Json.Serialization.Converters } } - public override void Write(Span escapedPropertyName, TValue value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, TValue value, Utf8JsonWriter writer) { if (TreatAsString) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs index 0ddac2d..16fe1d5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs @@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(short value, ref Utf8JsonWriter writer) + public override void Write(short value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, short value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, short value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs index 420d6b4..de21818 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs @@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetInt32(out value); } - public override void Write(int value, ref Utf8JsonWriter writer) + public override void Write(int value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, int value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, int value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs index d5ca306..b9d18dd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs @@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetInt64(out value); } - public override void Write(long value, ref Utf8JsonWriter writer) + public override void Write(long value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, long value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, long value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs index 4bbb0ea..786a740 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs @@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(sbyte value, ref Utf8JsonWriter writer) + public override void Write(sbyte value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, sbyte value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, sbyte value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs index 48f75b7..32093e3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs @@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(float value, ref Utf8JsonWriter writer) + public override void Write(float value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, float value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, float value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs index e892602..f5120cf 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs @@ -20,12 +20,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(string value, ref Utf8JsonWriter writer) + public override void Write(string value, Utf8JsonWriter writer) { writer.WriteStringValue(value); } - public override void Write(Span escapedPropertyName, string value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, string value, Utf8JsonWriter writer) { writer.WriteString(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs index a169731..cb232e6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs @@ -22,12 +22,12 @@ namespace System.Text.Json.Serialization.Converters return true; } - public override void Write(ushort value, ref Utf8JsonWriter writer) + public override void Write(ushort value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, ushort value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, ushort value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs index 1337c56..94e7dfc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs @@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetUInt32(out value); } - public override void Write(uint value, ref Utf8JsonWriter writer) + public override void Write(uint value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, uint value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, uint value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs index 60ff3d1..7a52f4b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs @@ -19,12 +19,12 @@ namespace System.Text.Json.Serialization.Converters return reader.TryGetUInt64(out value); } - public override void Write(ulong value, ref Utf8JsonWriter writer) + public override void Write(ulong value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, ulong value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, ulong value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 3624b6f..448f5eb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -255,7 +255,8 @@ namespace System.Text.Json.Serialization internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options); - internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer); - internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer); + internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); + + internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index a234ed4..ce4a94f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -82,13 +82,13 @@ namespace System.Text.Json.Serialization } // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call. - internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (current.Enumerator != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); - propertyInfo.WriteEnumerable(options, ref current, ref writer); + propertyInfo.WriteEnumerable(options, ref current, writer); } else if (ShouldSerialize) { @@ -117,17 +117,17 @@ namespace System.Text.Json.Serialization { if (_escapedName != null) { - ValueConverter.Write(_escapedName, value, ref writer); + ValueConverter.Write(_escapedName, value, writer); } else { - ValueConverter.Write(value, ref writer); + ValueConverter.Write(value, writer); } } } } - internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (ValueConverter != null) { @@ -150,7 +150,7 @@ namespace System.Text.Json.Serialization } else { - ValueConverter.Write(value, ref writer); + ValueConverter.Write(value, writer); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index c159722..44cc436 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -80,13 +80,13 @@ namespace System.Text.Json.Serialization } // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call. - internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (current.Enumerator != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); - propertyInfo.WriteEnumerable(options, ref current, ref writer); + propertyInfo.WriteEnumerable(options, ref current, writer); } else if (ShouldSerialize) { @@ -115,17 +115,17 @@ namespace System.Text.Json.Serialization { if (_escapedName != null) { - ValueConverter.Write(_escapedName, value.GetValueOrDefault(), ref writer); + ValueConverter.Write(_escapedName, value.GetValueOrDefault(), writer); } else { - ValueConverter.Write(value.GetValueOrDefault(), ref writer); + ValueConverter.Write(value.GetValueOrDefault(), writer); } } } } - internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (ValueConverter != null) { @@ -148,7 +148,7 @@ namespace System.Text.Json.Serialization } else { - ValueConverter.Write(value.GetValueOrDefault(), ref writer); + ValueConverter.Write(value.GetValueOrDefault(), writer); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs index 419e379..da494ef 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs @@ -9,18 +9,10 @@ namespace System.Text.Json.Serialization { public static partial class JsonSerializer { - private static bool WriteEnumerable( - JsonSerializerOptions options, - ref Utf8JsonWriter writer, - ref WriteStack state) - { - return HandleEnumerable(state.Current.JsonClassInfo.ElementClassInfo, options, ref writer, ref state); - } - private static bool HandleEnumerable( JsonClassInfo elementClassInfo, JsonSerializerOptions options, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ref WriteStack state) { Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable); @@ -66,7 +58,7 @@ namespace System.Text.Json.Serialization if (elementClassInfo.ClassType == ClassType.Value) { - elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, ref writer); + elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, writer); } else if (state.Current.Enumerator.Current == null) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index ee4b796..17c5c1c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -10,7 +10,7 @@ namespace System.Text.Json.Serialization { private static bool WriteObject( JsonSerializerOptions options, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ref WriteStack state) { JsonClassInfo classInfo = state.Current.JsonClassInfo; @@ -32,7 +32,7 @@ namespace System.Text.Json.Serialization // Determine if we are done enumerating properties. if (state.Current.PropertyIndex != classInfo.PropertyCount) { - HandleObject(options, ref writer, ref state); + HandleObject(options, writer, ref state); return false; } @@ -52,7 +52,7 @@ namespace System.Text.Json.Serialization private static bool HandleObject( JsonSerializerOptions options, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ref WriteStack state) { Debug.Assert( @@ -76,7 +76,7 @@ namespace System.Text.Json.Serialization if (jsonPropertyInfo.ClassType == ClassType.Value) { - jsonPropertyInfo.Write(options, ref state.Current, ref writer); + jsonPropertyInfo.Write(options, ref state.Current, writer); state.Current.NextProperty(); return true; } @@ -84,7 +84,7 @@ namespace System.Text.Json.Serialization // A property that returns an enumerator keeps the same stack frame. if (jsonPropertyInfo.ClassType == ClassType.Enumerable) { - bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, ref writer, ref state); + bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); if (endOfEnumerable) { state.Current.NextProperty(); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs deleted file mode 100644 index 5024f26..0000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Serialization -{ - public static partial class JsonSerializer - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool WriteValue( - JsonSerializerOptions options, - ref Utf8JsonWriter writer, - ref WriteStackFrame current) - { - Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value); - - current.JsonPropertyInfo.Write(options, ref current, ref writer); - return true; - } - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs index 66d45d4..ab80dd5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Buffers; using System.Diagnostics; namespace System.Text.Json.Serialization @@ -55,15 +54,6 @@ namespace System.Text.Json.Serialization } } - private static void WriteNull( - ref JsonWriterState writerState, - IBufferWriter bufferWriter) - { - Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState); - writer.WriteNullValue(); - writer.Flush(true); - } - private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOptions options) { if (options == null) @@ -73,7 +63,7 @@ namespace System.Text.Json.Serialization byte[] result; - using (var output = new ArrayBufferWriter(options.DefaultBufferSize)) + using (var output = new PooledBufferWriter(options.DefaultBufferSize)) { WriteCore(output, value, type, options); result = output.WrittenMemory.ToArray(); @@ -91,7 +81,7 @@ namespace System.Text.Json.Serialization string result; - using (var output = new ArrayBufferWriter(options.DefaultBufferSize)) + using (var output = new PooledBufferWriter(options.DefaultBufferSize)) { WriteCore(output, value, type, options); result = JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); @@ -100,12 +90,11 @@ namespace System.Text.Json.Serialization return result; } - private static void WriteCore(ArrayBufferWriter output, object value, Type type, JsonSerializerOptions options) + private static void WriteCore(PooledBufferWriter output, object value, Type type, JsonSerializerOptions options) { Debug.Assert(type != null || value == null); - var writerState = new JsonWriterState(options.GetWriterOptions()); - var writer = new Utf8JsonWriter(output, writerState); + var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); if (value == null) { @@ -123,10 +112,10 @@ namespace System.Text.Json.Serialization state.Current.Initialize(type, options); state.Current.CurrentValue = value; - Write(ref writer, -1, options, ref state); + Write(writer, -1, options, ref state); } - writer.Flush(isFinalBlock: true); + writer.Flush(); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs index 6987a7c..6a53e3a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs @@ -50,13 +50,16 @@ namespace System.Text.Json.Serialization options = JsonSerializerOptions.s_defaultOptions; } - var writerState = new JsonWriterState(options.GetWriterOptions()); + JsonWriterOptions writerOptions = options.GetWriterOptions(); - using (var bufferWriter = new ArrayBufferWriter(options.DefaultBufferSize)) + using (var bufferWriter = new PooledBufferWriter(options.DefaultBufferSize)) + using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions)) { if (value == null) { - WriteNull(ref writerState, bufferWriter); + writer.WriteNullValue(); + writer.Flush(); + #if BUILDING_INBOX_LIBRARY await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false); #else @@ -82,7 +85,9 @@ namespace System.Text.Json.Serialization { flushThreshold = (int)(bufferWriter.Capacity * .9); //todo: determine best value here - isFinalBlock = Write(ref writerState, bufferWriter, flushThreshold, options, ref state); + isFinalBlock = Write(writer, flushThreshold, options, ref state); + writer.Flush(); + #if BUILDING_INBOX_LIBRARY await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false); #else diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs index 0b64163..1913e73 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs @@ -9,33 +9,12 @@ namespace System.Text.Json.Serialization { public static partial class JsonSerializer { - private static bool Write( - ref JsonWriterState writerState, - IBufferWriter bufferWriter, - int flushThreshold, - JsonSerializerOptions options, - ref WriteStack state) - { - Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState); - - bool isFinalBlock = Write( - ref writer, - flushThreshold, - options, - ref state); - - writer.Flush(isFinalBlock: isFinalBlock); - writerState = writer.GetCurrentState(); - - return isFinalBlock; - } - // There are three conditions to consider for an object (primitive value, enumerable or object) being processed here: // 1) The object type was specified as the root-level return type to a Parse\Read method. // 2) The object is property on a parent object. // 3) The object is an element in an enumerable. private static bool Write( - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, int flushThreshold, JsonSerializerOptions options, ref WriteStack state) @@ -44,26 +23,29 @@ namespace System.Text.Json.Serialization bool finishedSerializing; do { - switch (state.Current.JsonClassInfo.ClassType) + WriteStackFrame current = state.Current; + switch (current.JsonClassInfo.ClassType) { case ClassType.Enumerable: - finishedSerializing = WriteEnumerable(options, ref writer, ref state); + finishedSerializing = HandleEnumerable(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); break; case ClassType.Value: - finishedSerializing = WriteValue(options, ref writer, ref state.Current); + Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value); + current.JsonPropertyInfo.Write(options, ref current, writer); + finishedSerializing = true; break; case ClassType.Object: - finishedSerializing = WriteObject(options, ref writer, ref state); + finishedSerializing = WriteObject(options, writer, ref state); break; default: Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown); // Treat typeof(object) as an empty object. - finishedSerializing = WriteObject(options, ref writer, ref state); + finishedSerializing = WriteObject(options, writer, ref state); break; } - if (flushThreshold >= 0 && writer.BytesWritten > flushThreshold) + if (flushThreshold >= 0 && writer.BytesPending > flushThreshold) { return false; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs index 35d9586..3598e34 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.Serialization.Policies internal abstract class JsonValueConverter { public abstract bool TryRead(Type valueType, ref Utf8JsonReader reader, out TValue value); - public abstract void Write(TValue value, ref Utf8JsonWriter writer); - public abstract void Write(Span escapedPropertyName, TValue value, ref Utf8JsonWriter writer); + public abstract void Write(TValue value, Utf8JsonWriter writer); + public abstract void Write(Span escapedPropertyName, TValue value, Utf8JsonWriter writer); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs similarity index 93% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs index 7524f36..b3f1894 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs @@ -7,21 +7,23 @@ using System.Diagnostics; namespace System.Text.Json.Serialization { - // Note: this is currently an internal class that will be replaced with a shared version. - internal sealed class ArrayBufferWriter : IBufferWriter, IDisposable + /// + /// This is an implementation detail and MUST NOT be called by source-package consumers. + /// + internal sealed class PooledBufferWriter : IBufferWriter, IDisposable { private T[] _rentedBuffer; private int _index; private const int MinimumBufferSize = 256; - public ArrayBufferWriter() + public PooledBufferWriter() { _rentedBuffer = ArrayPool.Shared.Rent(MinimumBufferSize); _index = 0; } - public ArrayBufferWriter(int initialCapacity) + public PooledBufferWriter(int initialCapacity) { if (initialCapacity <= 0) throw new ArgumentException(nameof(initialCapacity)); @@ -101,7 +103,7 @@ namespace System.Text.Json.Serialization private void CheckIfDisposed() { if (_rentedBuffer == null) - ThrowHelper.ThrowObjectDisposedException(nameof(ArrayBufferWriter)); + ThrowHelper.ThrowObjectDisposedException(nameof(PooledBufferWriter)); } public void Advance(int count) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index cdbd7a4..4d9abbd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -45,17 +45,9 @@ namespace System.Text.Json throw GetArgumentException(SR.SpecialNumberValuesNotSupported); } - public static void ThrowArgumentException(ExceptionResource resource, int minimumSize = 0) + public static void ThrowInvalidOperationException_NeedLargerSpan() { - if (resource == ExceptionResource.FailedToGetLargerSpan) - { - throw GetArgumentException(SR.FailedToGetLargerSpan); - } - else - { - Debug.Assert(resource == ExceptionResource.FailedToGetMinimumSizeSpan); - throw GetArgumentException(SR.Format(SR.FailedToGetMinimumSizeSpan, minimumSize)); - } + throw GetInvalidOperationException(SR.FailedToGetLargerSpan); } public static void ThrowArgumentException(ReadOnlySpan propertyName, ReadOnlySpan value) @@ -352,6 +344,11 @@ namespace System.Text.Json throw GetInvalidOperationException(resource, currentDepth, token, tokenType); } + public static void ThrowArgumentException_InvalidCommentValue() + { + throw new ArgumentException(SR.CannotWriteCommentWithEmbeddedDelimiter); + } + public static void ThrowArgumentException_InvalidUTF8(ReadOnlySpan value) { var builder = new StringBuilder(); @@ -532,8 +529,6 @@ namespace System.Text.Json CannotStartObjectArrayAfterPrimitiveOrClose, CannotWriteValueWithinObject, CannotWriteValueAfterPrimitive, - FailedToGetMinimumSizeSpan, - FailedToGetLargerSpan, CannotWritePropertyWithinArray, ExpectedJsonTokens, TrailingCommaNotAllowedBeforeArrayEnd, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs index c87795c..82b67a4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs @@ -9,40 +9,25 @@ namespace System.Text.Json { internal static partial class JsonWriterHelper { - public static bool TryWriteIndentation(Span buffer, int indent, out int bytesWritten) + public static void WriteIndentation(Span buffer, int indent) { Debug.Assert(indent % JsonConstants.SpacesPerIndent == 0); + Debug.Assert(buffer.Length >= indent); - if (buffer.Length >= indent) - { - // Based on perf tests, the break-even point where vectorized Fill is faster - // than explicitly writing the space in a loop is 8. - if (indent < 8) - { - int i = 0; - while (i < indent) - { - buffer[i++] = JsonConstants.Space; - buffer[i++] = JsonConstants.Space; - } - } - else - { - buffer.Slice(0, indent).Fill(JsonConstants.Space); - } - bytesWritten = indent; - return true; - } - else + // Based on perf tests, the break-even point where vectorized Fill is faster + // than explicitly writing the space in a loop is 8. + if (indent < 8) { int i = 0; - while (i < buffer.Length - 1) + while (i < indent) { buffer[i++] = JsonConstants.Space; buffer[i++] = JsonConstants.Space; } - bytesWritten = i; - return false; + } + else + { + buffer.Slice(0, indent).Fill(JsonConstants.Space); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs deleted file mode 100644 index 52e98d4..0000000 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.Text.Json -{ - /// - /// Defines an opaque type that holds and saves all the relevant state information which must be provided - /// to the to continue writing after completing a partial write. - /// - /// - /// This type is required to support reentrancy when writing incomplete data, and to continue - /// writing in chunks. Unlike the , which is a ref struct, - /// this type can survive across async/await boundaries and hence this type is required to provide - /// support for writing more JSON text asynchronously before continuing with a new instance of the . - /// - public struct JsonWriterState - { - internal long _bytesWritten; - internal long _bytesCommitted; - internal bool _inObject; - internal bool _isNotPrimitive; - internal JsonTokenType _tokenType; - internal int _currentDepth; - internal JsonWriterOptions _writerOptions; - internal BitStack _bitStack; - - /// - /// Returns the total amount of bytes written by the so far. - /// This includes data that has been written beyond what has already been committed. - /// - public long BytesWritten => _bytesWritten; - - /// - /// Returns the total amount of bytes committed to the output by the so far. - /// This is how much the IBufferWriter has advanced. - /// - public long BytesCommitted => _bytesCommitted; - - /// - /// Constructs a new instance. - /// - /// Defines the customized behavior of the - /// By default, the writes JSON minimized (i.e. with no extra whitespace) - /// and validates that the JSON being written is structurally valid according to JSON RFC. - /// - /// An instance of this state must be passed to the ctor with the output destination. - /// Unlike the , which is a ref struct, the state can survive - /// across async/await boundaries and hence this type is required to provide support for reading - /// in more data asynchronously before continuing with a new instance of the . - /// - public JsonWriterState(JsonWriterOptions options = default) - { - _bytesWritten = default; - _bytesCommitted = default; - _inObject = default; - _isNotPrimitive = default; - _tokenType = default; - _currentDepth = default; - _writerOptions = options; - - // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. - // This way we avoid allocations in the common, default cases, and allocate lazily. - _bitStack = default; - } - - /// - /// Gets the custom behavior when writing JSON using - /// the which indicates whether to format the output - /// while writing and whether to skip structural JSON validation or not. - /// - public JsonWriterOptions Options => _writerOptions; - } -} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index de9873b..065c72f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000. /// - public void WriteString(string propertyName, DateTime value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, DateTime value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ namespace System.Text.Json /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000. /// - public void WriteString(ReadOnlySpan propertyName, DateTime value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, DateTime value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringByOptions(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -65,7 +62,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ namespace System.Text.Json /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000. /// - public void WriteString(ReadOnlySpan utf8PropertyName, DateTime value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, DateTime value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringByOptions(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -96,7 +88,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan propertyName, DateTime value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -206,7 +178,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, DateTime value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -218,76 +190,180 @@ namespace System.Text.Json private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTime value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTime value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); - WriteStringValue(value, ref idx); + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - Advance(idx); - } + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); - } + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(DateTime value, ref int idx) + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - FormatLoop(value, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - } - private void FormatLoop(DateTime value, ref int idx) - { - Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); Debug.Assert(result); - JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; + } + + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); - if (_buffer.Length - idx < bytesWritten) + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, bytesWritten); + Grow(maxRequired); } - tempSpan.Slice(0, bytesWritten).CopyTo(_buffer.Slice(idx)); + Span output = _memory.Span; - idx += bytesWritten; - } + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O'); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index 95cb857..02474bd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000-07:00. /// - public void WriteString(string propertyName, DateTimeOffset value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, DateTimeOffset value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ namespace System.Text.Json /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000-07:00. /// - public void WriteString(ReadOnlySpan propertyName, DateTimeOffset value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, DateTimeOffset value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringByOptions(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -65,7 +62,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ namespace System.Text.Json /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000-07:00. /// - public void WriteString(ReadOnlySpan utf8PropertyName, DateTimeOffset value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, DateTimeOffset value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringByOptions(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -96,7 +88,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan propertyName, DateTimeOffset value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -206,7 +178,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, DateTimeOffset value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -218,74 +190,180 @@ namespace System.Text.Json private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); - WriteStringValue(value, ref idx); + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - Advance(idx); - } + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); - } + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(DateTimeOffset value, ref int idx) + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - FormatLoop(value, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - } - private void FormatLoop(DateTimeOffset value, ref int idx) - { - Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); Debug.Assert(result); - JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; + } + + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - if (_buffer.Length - idx < bytesWritten) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx, bytesWritten); + output[BytesPending++] = JsonConstants.ListSeparator; } - tempSpan.Slice(0, bytesWritten).CopyTo(_buffer.Slice(idx)); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; - idx += bytesWritten; + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index f03e84d..be3a8f5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(string propertyName, decimal value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, decimal value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan propertyName, decimal value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, decimal value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -65,7 +62,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, decimal value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, decimal value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -96,7 +88,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan propertyName, decimal value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -206,7 +178,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, decimal value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -218,49 +190,152 @@ namespace System.Text.Json private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDecimalLength - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDecimalLength - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteNumberValueFormatLoop(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); - Advance(idx); + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(decimal value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDecimalLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index ea19ce3..c8c67ad 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(string propertyName, double value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, double value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,19 +47,12 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan propertyName, double value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, double value) { JsonWriterHelper.ValidateProperty(propertyName); JsonWriterHelper.ValidateDouble(value); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -66,7 +63,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -76,19 +75,12 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, double value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, double value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); JsonWriterHelper.ValidateDouble(value); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -98,7 +90,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -114,7 +106,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -134,21 +126,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -167,21 +149,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -195,7 +167,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan propertyName, double value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -208,7 +180,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, double value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -220,49 +192,152 @@ namespace System.Text.Json private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDoubleLength - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDoubleLength - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteNumberValueFormatLoop(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); - Advance(idx); + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(double value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDoubleLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index 5853265..7595cd4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(string propertyName, float value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, float value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,19 +47,12 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan propertyName, float value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, float value) { JsonWriterHelper.ValidateProperty(propertyName); JsonWriterHelper.ValidateSingle(value); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -66,7 +63,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -76,19 +75,12 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, float value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, float value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); JsonWriterHelper.ValidateSingle(value); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -98,7 +90,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -114,7 +106,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -134,21 +126,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -167,21 +149,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -195,7 +167,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan propertyName, float value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -205,64 +177,167 @@ namespace System.Text.Json } } - private void WriteNumberByOptions(ReadOnlySpan propertyName, float value) + private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, float value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { - WriteNumberIndented(propertyName, value); + WriteNumberIndented(utf8PropertyName, value); } else { - WriteNumberMinimized(propertyName, value); + WriteNumberMinimized(utf8PropertyName, value); } } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatSingleLength - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - WriteNumberValueFormatLoop(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatSingleLength - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - WriteNumberValueFormatLoop(value, ref idx); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(float value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatSingleLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + WriteNewLine(output); } - idx += bytesWritten; + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs index 23233e0..f7af6fc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs @@ -3,18 +3,20 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Buffers.Text; using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -30,9 +32,11 @@ namespace System.Text.Json internal void WriteNumber(ReadOnlySpan propertyName, ReadOnlySpan utf8FormattedNumber) { JsonWriterHelper.ValidateProperty(propertyName); + JsonWriterHelper.ValidateValue(utf8FormattedNumber); JsonWriterHelper.ValidateNumber(utf8FormattedNumber); WriteNumberEscape(propertyName, utf8FormattedNumber); + SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; } @@ -42,6 +46,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -57,9 +64,11 @@ namespace System.Text.Json internal void WriteNumber(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8FormattedNumber) { JsonWriterHelper.ValidateProperty(utf8PropertyName); + JsonWriterHelper.ValidateValue(utf8FormattedNumber); JsonWriterHelper.ValidateNumber(utf8FormattedNumber); WriteNumberEscape(utf8PropertyName, utf8FormattedNumber); + SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; } @@ -68,7 +77,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -84,7 +93,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -104,21 +113,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -137,21 +136,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,74 +154,27 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan propertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { - WriteNumberIndented(propertyName, value); + WriteLiteralIndented(propertyName, value); } else { - WriteNumberMinimized(propertyName, value); + WriteLiteralMinimized(propertyName, value); } } private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { - WriteNumberIndented(utf8PropertyName, value); + WriteLiteralIndented(utf8PropertyName, value); } else { - WriteNumberMinimized(utf8PropertyName, value); - } - } - - private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameMinimized(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameMinimized(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberValueFormatLoop(ReadOnlySpan value, ref int idx) - { - if (_buffer.Length - idx - value.Length < 0) - { - AdvanceAndGrow(ref idx, value.Length); + WriteLiteralMinimized(utf8PropertyName, value); } - - value.CopyTo(_buffer.Slice(idx)); - idx += value.Length; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index c201b6a..d1e4c69 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn. /// - public void WriteString(string propertyName, Guid value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, Guid value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn. /// - public void WriteString(ReadOnlySpan propertyName, Guid value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, Guid value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringByOptions(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -65,7 +62,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn. /// - public void WriteString(ReadOnlySpan utf8PropertyName, Guid value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, Guid value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringByOptions(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -96,7 +88,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan propertyName, Guid value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -206,7 +178,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, Guid value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -218,66 +190,168 @@ namespace System.Text.Json private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatGuidLength - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatGuidLength - 6); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteStringValue(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteStringValue(Guid value, ref int idx) - { - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - FormatLoop(value, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - } - private void FormatLoop(Guid value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatGuidLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + WriteNewLine(output); } - idx += bytesWritten; + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs index 9c1867c..4d6f04d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidatePropertyNameAndDepth(ReadOnlySpan propertyName) @@ -28,7 +28,7 @@ namespace System.Text.Json [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidateWritingProperty() { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { if (!_inObject) { @@ -41,7 +41,7 @@ namespace System.Text.Json [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidateWritingProperty(byte token) { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { if (!_inObject) { @@ -52,212 +52,154 @@ namespace System.Text.Json } } - private int WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + Debug.Assert(escapedPropertyName.Length < int.MaxValue - 5); - if (_buffer.Length <= idx) + int minRequired = escapedPropertyName.Length + 4; // 2 quotes, 1 colon, and 1 start token + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - CopyLoop(escapedPropertyName, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.Quote; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.KeyValueSeperator; + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - return idx; + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = token; } - private int WritePropertyNameIndented(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 6 - s_newLineLength); - int indent = Indentation; - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } + int minRequired = indent + escapedPropertyName.Length + 5; // 2 quotes, 1 colon, 1 space, and 1 start token + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - CopyLoop(escapedPropertyName, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.KeyValueSeperator; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Space; + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - return idx; + output[BytesPending++] = JsonConstants.Quote; + + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + output[BytesPending++] = token; } - private int WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 5); - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Quote; + // All ASCII, 2 quotes, 1 colon, and 1 start token => escapedPropertyName.Length + 4 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 5; - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedPropertyName); - int partialConsumed = 0; - while (true) + if (_memory.Length - BytesPending < maxRequired) { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Quote; + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Quote; - return idx; + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = token; } - private int WritePropertyNameIndented(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 6 - s_newLineLength); - int indent = Indentation; - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } + // All ASCII, 2 quotes, 1 colon, 1 space, and 1 start token => indent + escapedPropertyName.Length + 5 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6 + s_newLineLength; - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedPropertyName); - int partialConsumed = 0; - while (true) - { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); - } + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.KeyValueSeperator; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Space; + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - return idx; + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + output[BytesPending++] = token; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void TranscodeAndWrite(ReadOnlySpan escapedPropertyName, Span output) + { + ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedPropertyName); + OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int consumed, out int written); + Debug.Assert(status == OperationStatus.Done); + Debug.Assert(consumed == byteSpan.Length); + BytesPending += written; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index 13f3a16..f5e2a14 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -7,47 +7,44 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteNull(string propertyName, bool escape = true) - => WriteNull(propertyName.AsSpan(), escape); + public void WriteNull(string propertyName) + => WriteNull(propertyName.AsSpan()); /// /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteNull(ReadOnlySpan propertyName, bool escape = true) + public void WriteNull(ReadOnlySpan propertyName) { JsonWriterHelper.ValidateProperty(propertyName); ReadOnlySpan span = JsonConstants.NullValue; - if (escape) - { - WriteLiteralEscape(propertyName, span); - } - else - { - WriteLiteralByOptions(propertyName, span); - } + WriteLiteralEscape(propertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Null; @@ -57,27 +54,22 @@ namespace System.Text.Json /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteNull(ReadOnlySpan utf8PropertyName, bool escape = true) + public void WriteNull(ReadOnlySpan utf8PropertyName) { JsonWriterHelper.ValidateProperty(utf8PropertyName); ReadOnlySpan span = JsonConstants.NullValue; - if (escape) - { - WriteLiteralEscape(utf8PropertyName, span); - } - else - { - WriteLiteralByOptions(utf8PropertyName, span); - } + WriteLiteralEscape(utf8PropertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Null; @@ -88,42 +80,39 @@ namespace System.Text.Json /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON literal "true" or "false" as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteBoolean(string propertyName, bool value, bool escape = true) - => WriteBoolean(propertyName.AsSpan(), value, escape); + public void WriteBoolean(string propertyName, bool value) + => WriteBoolean(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON literal "true" or "false" as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteBoolean(ReadOnlySpan propertyName, bool value, bool escape = true) + public void WriteBoolean(ReadOnlySpan propertyName, bool value) { JsonWriterHelper.ValidateProperty(propertyName); ReadOnlySpan span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue; - if (escape) - { - WriteLiteralEscape(propertyName, span); - } - else - { - WriteLiteralByOptions(propertyName, span); - } + WriteLiteralEscape(propertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = value ? JsonTokenType.True : JsonTokenType.False; @@ -134,27 +123,22 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON literal "true" or "false" as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteBoolean(ReadOnlySpan utf8PropertyName, bool value, bool escape = true) + public void WriteBoolean(ReadOnlySpan utf8PropertyName, bool value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); ReadOnlySpan span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue; - if (escape) - { - WriteLiteralEscape(utf8PropertyName, span); - } - else - { - WriteLiteralByOptions(utf8PropertyName, span); - } + WriteLiteralEscape(utf8PropertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = value ? JsonTokenType.True : JsonTokenType.False; @@ -164,7 +148,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -180,7 +164,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -200,21 +184,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value); @@ -233,21 +207,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value); @@ -261,49 +225,177 @@ namespace System.Text.Json private void WriteLiteralByOptions(ReadOnlySpan propertyName, ReadOnlySpan value) { ValidateWritingProperty(); - int idx; - if (_writerOptions.Indented) + if (Options.Indented) + { + WriteLiteralIndented(propertyName, value); + } + else + { + WriteLiteralMinimized(propertyName, value); + } + } + + private void WriteLiteralByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) + { + ValidateWritingProperty(); + if (Options.Indented) { - idx = WritePropertyNameIndented(propertyName); + WriteLiteralIndented(utf8PropertyName, value); } else { - idx = WritePropertyNameMinimized(propertyName); + WriteLiteralMinimized(utf8PropertyName, value); + } + } + + private void WriteLiteralMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) + { + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - value.Length - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + value.Length + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); } - if (value.Length > _buffer.Length - idx) + Span output = _memory.Span; + + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx, value.Length); + output[BytesPending++] = JsonConstants.ListSeparator; } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - value.CopyTo(_buffer.Slice(idx)); - idx += value.Length; + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; } - private void WriteLiteralByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) + private void WriteLiteralMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) { - ValidateWritingProperty(); - int idx; - if (_writerOptions.Indented) + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - value.Length - 4); + + int minRequired = escapedPropertyName.Length + value.Length + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) { - idx = WritePropertyNameIndented(utf8PropertyName); + Grow(maxRequired); } - else + + Span output = _memory.Span; + + if (_currentDepth < 0) { - idx = WritePropertyNameMinimized(utf8PropertyName); + output[BytesPending++] = JsonConstants.ListSeparator; } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - if (value.Length > _buffer.Length - idx) + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; + } + + private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + value.Length + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, value.Length); + Grow(maxRequired); } - value.CopyTo(_buffer.Slice(idx)); - idx += value.Length; + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; + } + + private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength); + + int minRequired = indent + escapedPropertyName.Length + value.Length + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; - Advance(idx); + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index be106a1..8ab3243 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(string propertyName, long value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, long value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan propertyName, long value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, long value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -65,7 +62,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, long value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, long value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -97,7 +89,9 @@ namespace System.Text.Json /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -107,15 +101,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(string propertyName, int value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), (long)value, escape); + public void WriteNumber(string propertyName, int value) + => WriteNumber(propertyName.AsSpan(), (long)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -125,15 +121,17 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan propertyName, int value, bool escape = true) - => WriteNumber(propertyName, (long)value, escape); + public void WriteNumber(ReadOnlySpan propertyName, int value) + => WriteNumber(propertyName, (long)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -143,14 +141,14 @@ namespace System.Text.Json /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, int value, bool escape = true) - => WriteNumber(utf8PropertyName, (long)value, escape); + public void WriteNumber(ReadOnlySpan utf8PropertyName, int value) + => WriteNumber(utf8PropertyName, (long)value); private void WriteNumberEscape(ReadOnlySpan propertyName, long value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -166,7 +164,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -186,21 +184,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -219,21 +207,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -247,7 +225,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan propertyName, long value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -260,7 +238,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, long value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -272,49 +250,152 @@ namespace System.Text.Json private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatInt64Length - 4); - WriteNumberValueFormatLoop(value, ref idx); + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 4; - Advance(idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatInt64Length - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(long value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatInt64Length); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index f90c35b..1036779 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -9,49 +9,44 @@ using System.Runtime.InteropServices; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(string propertyName, string value, bool escape = true) - => WriteString(propertyName.AsSpan(), value.AsSpan(), escape); + public void WriteString(string propertyName, string value) + => WriteString(propertyName.AsSpan(), value.AsSpan()); /// /// Writes the UTF-16 property name and UTF-16 text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan value) { JsonWriterHelper.ValidatePropertyAndValue(propertyName, value); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringDontEscape(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -62,26 +57,20 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The UTF-8 encoded value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8Value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8Value) { JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, utf8Value); - if (escape) - { - WriteStringEscape(utf8PropertyName, utf8Value); - } - else - { - WriteStringDontEscape(utf8PropertyName, utf8Value); - } + WriteStringEscape(utf8PropertyName, utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -92,42 +81,37 @@ namespace System.Text.Json /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(string propertyName, ReadOnlySpan value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, ReadOnlySpan value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the UTF-8 property name and UTF-16 text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) { JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, value); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringDontEscape(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -138,42 +122,37 @@ namespace System.Text.Json /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-8 encoded value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(string propertyName, ReadOnlySpan utf8Value, bool escape = true) - => WriteString(propertyName.AsSpan(), utf8Value, escape); + public void WriteString(string propertyName, ReadOnlySpan utf8Value) + => WriteString(propertyName.AsSpan(), utf8Value); /// /// Writes the UTF-16 property name and UTF-8 text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-8 encoded value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan utf8Value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan utf8Value) { JsonWriterHelper.ValidatePropertyAndValue(propertyName, utf8Value); - if (escape) - { - WriteStringEscape(propertyName, utf8Value); - } - else - { - WriteStringDontEscape(propertyName, utf8Value); - } + WriteStringEscape(propertyName, utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -184,88 +163,34 @@ namespace System.Text.Json /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan propertyName, string value, bool escape = true) - => WriteString(propertyName, value.AsSpan(), escape); + public void WriteString(ReadOnlySpan propertyName, string value) + => WriteString(propertyName, value.AsSpan()); /// /// Writes the UTF-8 property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan utf8PropertyName, string value, bool escape = true) - => WriteString(utf8PropertyName, value.AsSpan(), escape); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan utf8Value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, utf8Value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, utf8Value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan utf8Value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, utf8Value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, utf8Value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, value); - } - } + public void WriteString(ReadOnlySpan utf8PropertyName, string value) + => WriteString(utf8PropertyName, value.AsSpan()); private void WriteStringEscapeValueOnly(ReadOnlySpan escapedPropertyName, ReadOnlySpan value, int firstEscapeIndex) { @@ -328,8 +253,8 @@ namespace System.Text.Json int valueIdx = JsonWriterHelper.NeedsEscaping(value); int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -347,8 +272,8 @@ namespace System.Text.Json int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -366,8 +291,8 @@ namespace System.Text.Json int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -385,8 +310,8 @@ namespace System.Text.Json int valueIdx = JsonWriterHelper.NeedsEscaping(value); int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -412,20 +337,21 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); value = escapedValue.Slice(0, written); } @@ -435,20 +361,21 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); propertyName = escapedPropertyName.Slice(0, written); } @@ -479,20 +406,21 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); utf8Value = escapedValue.Slice(0, written); } @@ -500,21 +428,23 @@ namespace System.Text.Json if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); + Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); utf8PropertyName = escapedPropertyName.Slice(0, written); } @@ -545,20 +475,21 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); utf8Value = escapedValue.Slice(0, written); } @@ -566,21 +497,23 @@ namespace System.Text.Json if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); + Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); propertyName = escapedPropertyName.Slice(0, written); } @@ -611,20 +544,21 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); value = escapedValue.Slice(0, written); } @@ -632,21 +566,23 @@ namespace System.Text.Json if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); + Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); utf8PropertyName = escapedPropertyName.Slice(0, written); } @@ -667,7 +603,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan propertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -680,7 +616,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8Value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, utf8Value); } @@ -693,7 +629,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan propertyName, ReadOnlySpan utf8Value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, utf8Value); } @@ -706,7 +642,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -716,122 +652,342 @@ namespace System.Text.Json } } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 6) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); + + // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - escapedValue.Length - 6); + + int minRequired = escapedPropertyName.Length + escapedValue.Length + 5; // 2 quotes for property name, 2 quotes for value, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 7 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); - WriteStringValue(escapedValue, ref idx); + // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - Advance(idx); + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteStringValue(escapedValue, ref idx); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - escapedValue.Length - 7 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + escapedValue.Length + 6; // 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - WriteStringValue(escapedValue, ref idx); + Span output = _memory.Span; - Advance(idx); - } + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(ReadOnlySpan escapedValue, ref int idx) + // TODO: https://github.com/dotnet/corefx/issues/36958 + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedValue); - int partialConsumed = 0; - while (true) + Span output = _memory.Span; + + if (_currentDepth < 0) { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.Quote; + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(ReadOnlySpan escapedValue, ref int idx) + // TODO: https://github.com/dotnet/corefx/issues/36958 + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - CopyLoop(escapedValue, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index ee85f7d..8ba0f3c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -8,14 +8,16 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -26,15 +28,17 @@ namespace System.Text.Json /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(string propertyName, ulong value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, ulong value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -45,18 +49,11 @@ namespace System.Text.Json /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan propertyName, ulong value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, ulong value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -67,7 +64,9 @@ namespace System.Text.Json /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -78,18 +77,11 @@ namespace System.Text.Json /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan utf8PropertyName, ulong value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, ulong value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -100,7 +92,9 @@ namespace System.Text.Json /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -111,15 +105,17 @@ namespace System.Text.Json /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(string propertyName, uint value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), (ulong)value, escape); + public void WriteNumber(string propertyName, uint value) + => WriteNumber(propertyName.AsSpan(), (ulong)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -130,15 +126,17 @@ namespace System.Text.Json /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan propertyName, uint value, bool escape = true) - => WriteNumber(propertyName, (ulong)value, escape); + public void WriteNumber(ReadOnlySpan propertyName, uint value) + => WriteNumber(propertyName, (ulong)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -149,14 +147,14 @@ namespace System.Text.Json /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan utf8PropertyName, uint value, bool escape = true) - => WriteNumber(utf8PropertyName, (ulong)value, escape); + public void WriteNumber(ReadOnlySpan utf8PropertyName, uint value) + => WriteNumber(utf8PropertyName, (ulong)value); private void WriteNumberEscape(ReadOnlySpan propertyName, ulong value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -172,7 +170,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -192,21 +190,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -225,21 +213,11 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -253,7 +231,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan propertyName, ulong value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -266,7 +244,7 @@ namespace System.Text.Json private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, ulong value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -278,49 +256,152 @@ namespace System.Text.Json private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatUInt64Length - 4); - WriteNumberValueFormatLoop(value, ref idx); + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 4; - Advance(idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatUInt64Length - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(ulong value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatUInt64Length); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs index dc749fa..ede1918 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs @@ -8,60 +8,49 @@ using System.Runtime.InteropServices; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { + private static char[] s_singleLineCommentDelimiter = new char[2] { '*', '/' }; + private static ReadOnlySpan SingleLineCommentDelimiterUtf8 => new byte[2] { (byte)'*', (byte)'/' }; + /// /// Writes the string text value (as a JSON comment). /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON comment within /*..*/. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The comment value is not escaped before writing. + /// /// - /// Thrown when the specified value is too large. + /// Thrown when the specified value is too large OR if the given string text value contains a comment delimiter (i.e. */). /// - public void WriteCommentValue(string value, bool escape = true) - => WriteCommentValue(value.AsSpan(), escape); + public void WriteCommentValue(string value) + => WriteCommentValue(value.AsSpan()); /// /// Writes the UTF-16 text value (as a JSON comment). /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON comment within /*..*/. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The comment value is not escaped before writing. + /// /// - /// Thrown when the specified value is too large. + /// Thrown when the specified value is too large OR if the given UTF-16 text value contains a comment delimiter (i.e. */). /// - public void WriteCommentValue(ReadOnlySpan value, bool escape = true) + public void WriteCommentValue(ReadOnlySpan value) { JsonWriterHelper.ValidateValue(value); - if (escape) + if (value.IndexOf(s_singleLineCommentDelimiter) != -1) { - WriteCommentEscape(value); + ThrowHelper.ThrowArgumentException_InvalidCommentValue(); } - else - { - WriteCommentByOptions(value); - } - } - private void WriteCommentEscape(ReadOnlySpan value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(value); - - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - - if (valueIdx != -1) - { - WriteCommentEscapeValue(value, valueIdx); - } - else - { - WriteCommentByOptions(value); - } + WriteCommentByOptions(value); } private void WriteCommentByOptions(ReadOnlySpan value) { - if (_writerOptions.Indented) + if (Options.Indented) { WriteCommentIndented(value); } @@ -71,115 +60,96 @@ namespace System.Text.Json } } - private void WriteCommentMinimized(ReadOnlySpan escapedValue) - { - int idx = 0; - - WriteCommentValue(escapedValue, ref idx); - - Advance(idx); - } - - private void WriteCommentIndented(ReadOnlySpan escapedValue) + private void WriteCommentMinimized(ReadOnlySpan value) { - int idx = 0; + Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 4); - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + // All ASCII, /*...*/ => escapedValue.Length + 4 + // Optionally, up to 3x growth when transcoding + int maxRequired = (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4; - int indent = Indentation; - while (true) + if (_memory.Length - BytesPending < maxRequired) { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - WriteCommentValue(escapedValue, ref idx); + Span output = _memory.Span; - Advance(idx); + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + + ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(value); + OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int _, out int written); + Debug.Assert(status != OperationStatus.DestinationTooSmall); + BytesPending += written; + + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } - private void WriteCommentEscapeValue(ReadOnlySpan value, int firstEscapeIndexVal) + private void WriteCommentIndented(ReadOnlySpan value) { - Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length); - Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < value.Length); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - char[] valueArray = null; + Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - s_newLineLength); - int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); + // All ASCII, /*...*/ => escapedValue.Length + 4 + // Optionally, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4 + s_newLineLength; - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else + if (_memory.Length - BytesPending < maxRequired) { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedValue = new Span(ptr, length); - } + Grow(maxRequired); } - JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); - WriteCommentByOptions(escapedValue.Slice(0, written)); + Span output = _memory.Span; - if (valueArray != null) + if (_tokenType != JsonTokenType.None) { - ArrayPool.Shared.Return(valueArray); + WriteNewLine(output); } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + + ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(value); + OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int _, out int written); + Debug.Assert(status != OperationStatus.DestinationTooSmall); + BytesPending += written; + + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } /// /// Writes the UTF-8 text value (as a JSON comment). /// /// The UTF-8 encoded value to be written as a JSON comment within /*..*/. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The comment value is not escaped before writing. + /// /// - /// Thrown when the specified value is too large. + /// Thrown when the specified value is too large OR if the given UTF-8 text value contains a comment delimiter (i.e. */). /// - public void WriteCommentValue(ReadOnlySpan utf8Value, bool escape = true) + public void WriteCommentValue(ReadOnlySpan utf8Value) { JsonWriterHelper.ValidateValue(utf8Value); - if (escape) + if (utf8Value.IndexOf(SingleLineCommentDelimiterUtf8) != -1) { - WriteCommentEscape(utf8Value); + ThrowHelper.ThrowArgumentException_InvalidCommentValue(); } - else - { - WriteCommentByOptions(utf8Value); - } - } - private void WriteCommentEscape(ReadOnlySpan utf8Value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - - if (valueIdx != -1) - { - WriteCommentEscapeValue(utf8Value, valueIdx); - } - else - { - WriteCommentByOptions(utf8Value); - } + WriteCommentByOptions(utf8Value); } private void WriteCommentByOptions(ReadOnlySpan utf8Value) { - if (_writerOptions.Indented) + if (Options.Indented) { WriteCommentIndented(utf8Value); } @@ -189,127 +159,62 @@ namespace System.Text.Json } } - private void WriteCommentMinimized(ReadOnlySpan escapedValue) - { - int idx = 0; - - WriteCommentValue(escapedValue, ref idx); - - Advance(idx); - } - - private void WriteCommentIndented(ReadOnlySpan escapedValue) - { - int idx = 0; - WriteFormattingPreamble(ref idx); - - WriteCommentValue(escapedValue, ref idx); - - Advance(idx); - } - - private void WriteCommentEscapeValue(ReadOnlySpan utf8Value, int firstEscapeIndexVal) + private void WriteCommentMinimized(ReadOnlySpan utf8Value) { - Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length); - Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length); + Debug.Assert(utf8Value.Length < int.MaxValue - 4); - byte[] valueArray = null; + int maxRequired = utf8Value.Length + 4; // /*...*/ - int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else + if (_memory.Length - BytesPending < maxRequired) { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedValue = new Span(ptr, length); - } + Grow(maxRequired); } - JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); - WriteCommentByOptions(escapedValue.Slice(0, written)); + Span output = _memory.Span; - if (valueArray != null) - { - ArrayPool.Shared.Return(valueArray); - } + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; + + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } - private void WriteCommentValue(ReadOnlySpan escapedValue, ref int idx) + private void WriteCommentIndented(ReadOnlySpan utf8Value) { - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Slash; + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Asterisk; + Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - s_newLineLength); - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedValue); - int partialConsumed = 0; - while (true) - { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); - } + int minRequired = indent + utf8Value.Length + 4; // /*...*/ + int maxRequired = minRequired + s_newLineLength; // Optionally, 1-2 bytes for new line - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Asterisk; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Slash; - } + Span output = _memory.Span; - private void WriteCommentValue(ReadOnlySpan escapedValue, ref int idx) - { - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.Slash; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Asterisk; + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - CopyLoop(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Asterisk; + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs index d68d461..b62679a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON string) as an element of a JSON array. @@ -21,7 +23,7 @@ namespace System.Text.Json public void WriteStringValue(DateTime value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringValueIndented(value); } @@ -36,21 +38,72 @@ namespace System.Text.Json private void WriteStringValueMinimized(DateTime value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDateTimeOffsetLength + 3; // 2 quotes, and optionally, 1 list separator - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringValueIndented(DateTime value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } + + private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O'); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs index 3edbf64..45d9aeb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs @@ -3,10 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; + namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON string) as an element of a JSON array. @@ -21,7 +24,7 @@ namespace System.Text.Json public void WriteStringValue(DateTimeOffset value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringValueIndented(value); } @@ -36,21 +39,70 @@ namespace System.Text.Json private void WriteStringValueMinimized(DateTimeOffset value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDateTimeOffsetLength + 3; // 2 quotes, and optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - WriteStringValue(value, ref idx); + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringValueIndented(DateTimeOffset value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteStringValue(value, ref idx); + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs index 305cf85..2ad2b1d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -21,7 +23,7 @@ namespace System.Text.Json public void WriteNumberValue(decimal value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -36,21 +38,55 @@ namespace System.Text.Json private void WriteNumberValueMinimized(decimal value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDecimalLength + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(decimal value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatDecimalLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs index 54fc046..74315e7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -23,7 +25,7 @@ namespace System.Text.Json JsonWriterHelper.ValidateDouble(value); ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -38,21 +40,55 @@ namespace System.Text.Json private void WriteNumberValueMinimized(double value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDoubleLength + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(double value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs index ad8dd12..47aca25 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -23,7 +25,7 @@ namespace System.Text.Json JsonWriterHelper.ValidateSingle(value); ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -38,21 +40,55 @@ namespace System.Text.Json private void WriteNumberValueMinimized(float value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatSingleLength + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(float value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatSingleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs index a0f17ef..f5c2458 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs @@ -3,10 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -23,10 +24,11 @@ namespace System.Text.Json /// internal void WriteNumberValue(ReadOnlySpan utf8FormattedNumber) { + JsonWriterHelper.ValidateValue(utf8FormattedNumber); JsonWriterHelper.ValidateNumber(utf8FormattedNumber); - ValidateWritingValue(); - if (_writerOptions.Indented) + + if (Options.Indented) { WriteNumberValueIndented(utf8FormattedNumber); } @@ -39,23 +41,57 @@ namespace System.Text.Json _tokenType = JsonTokenType.Number; } - private void WriteNumberValueMinimized(ReadOnlySpan value) + private void WriteNumberValueMinimized(ReadOnlySpan utf8Value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = utf8Value.Length + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - WriteNumberValueFormatLoop(value, ref idx); + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } - private void WriteNumberValueIndented(ReadOnlySpan value) + private void WriteNumberValueIndented(ReadOnlySpan utf8Value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(utf8Value.Length < int.MaxValue - indent - 1 - s_newLineLength); + + int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs index a6d17ce..59b87f1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON string) as an element of a JSON array. @@ -21,7 +23,7 @@ namespace System.Text.Json public void WriteStringValue(Guid value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringValueIndented(value); } @@ -36,21 +38,64 @@ namespace System.Text.Json private void WriteStringValueMinimized(Guid value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatGuidLength + 3; // 2 quotes, and optionally, 1 list separator - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringValueIndented(Guid value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatGuidLength + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteStringValue(value, ref idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs index bdbd8f8..cc4986e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs @@ -6,11 +6,11 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { private void ValidateWritingValue() { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { if (_inObject) { @@ -26,44 +26,5 @@ namespace System.Text.Json } } } - - private int WriteCommaAndFormattingPreamble() - { - int idx = 0; - WriteListSeparator(ref idx); - WriteFormattingPreamble(ref idx); - return idx; - } - - private void WriteFormattingPreamble(ref int idx) - { - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); - - int indent = Indentation; - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } - } - - private void WriteListSeparator(ref int idx) - { - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } - } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs index 03d57f2..2a7abf4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; + namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the JSON literal "null" as an element of a JSON array. @@ -46,7 +48,7 @@ namespace System.Text.Json private void WriteLiteralByOptions(ReadOnlySpan utf8Value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteLiteralIndented(utf8Value); } @@ -58,21 +60,56 @@ namespace System.Text.Json private void WriteLiteralMinimized(ReadOnlySpan utf8Value) { - int idx = 0; - WriteListSeparator(ref idx); + Debug.Assert(utf8Value.Length <= 5); + + int maxRequired = utf8Value.Length + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - CopyLoop(utf8Value, ref idx); + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } private void WriteLiteralIndented(ReadOnlySpan utf8Value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + Debug.Assert(utf8Value.Length <= 5); + + int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - CopyLoop(utf8Value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs index d32224d..5f772ea 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -34,7 +36,7 @@ namespace System.Text.Json public void WriteNumberValue(long value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -49,21 +51,55 @@ namespace System.Text.Json private void WriteNumberValueMinimized(long value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatInt64Length + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(long value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs index e9513bb..c3422b1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs @@ -7,45 +7,42 @@ using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the string text value (as a JSON string) as an element of a JSON array. /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string element of a JSON array. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The value is escaped before writing. + /// /// /// Thrown when the specified value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStringValue(string value, bool escape = true) - => WriteStringValue(value.AsSpan(), escape); + public void WriteStringValue(string value) + => WriteStringValue(value.AsSpan()); /// /// Writes the UTF-16 text value (as a JSON string) as an element of a JSON array. /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string element of a JSON array. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The value is escaped before writing. + /// /// /// Thrown when the specified value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStringValue(ReadOnlySpan value, bool escape = true) + public void WriteStringValue(ReadOnlySpan value) { JsonWriterHelper.ValidateValue(value); - if (escape) - { - WriteStringEscape(value); - } - else - { - WriteStringByOptions(value); - } + WriteStringEscape(value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -55,7 +52,7 @@ namespace System.Text.Json { int valueIdx = JsonWriterHelper.NeedsEscaping(value); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < value.Length); if (valueIdx != -1) { @@ -70,7 +67,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(value); } @@ -80,23 +77,70 @@ namespace System.Text.Json } } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedValue) { - int idx = 0; - WriteListSeparator(ref idx); + Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 3); + + // All ASCII, 2 quotes => escapedValue.Length + 2 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3; - WriteStringValue(escapedValue, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - Advance(idx); + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedValue) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 3 - s_newLineLength); + + // All ASCII, 2 quotes => indent + escapedValue.Length + 2 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteStringValue(escapedValue, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringEscapeValue(ReadOnlySpan value, int firstEscapeIndexVal) @@ -108,21 +152,10 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedValue = new Span(ptr, length); - } - } + Span escapedValue = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (valueArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); WriteStringByOptions(escapedValue.Slice(0, written)); @@ -137,25 +170,20 @@ namespace System.Text.Json /// Writes the UTF-8 text value (as a JSON string) as an element of a JSON array. /// /// The UTF-8 encoded value to be written as a JSON string element of a JSON array. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The value is escaped before writing. + /// /// /// Thrown when the specified value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStringValue(ReadOnlySpan utf8Value, bool escape = true) + public void WriteStringValue(ReadOnlySpan utf8Value) { JsonWriterHelper.ValidateValue(utf8Value); - if (escape) - { - WriteStringEscape(utf8Value); - } - else - { - WriteStringByOptions(utf8Value); - } + WriteStringEscape(utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -165,7 +193,7 @@ namespace System.Text.Json { int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length); if (valueIdx != -1) { @@ -180,7 +208,7 @@ namespace System.Text.Json private void WriteStringByOptions(ReadOnlySpan utf8Value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8Value); } @@ -190,23 +218,70 @@ namespace System.Text.Json } } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedValue) { - int idx = 0; - WriteListSeparator(ref idx); + Debug.Assert(escapedValue.Length < int.MaxValue - 3); + + int minRequired = escapedValue.Length + 2; // 2 quotes + int maxRequired = minRequired + 1; // Optionally, 1 list separator - WriteStringValue(escapedValue, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - Advance(idx); + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedValue) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - s_newLineLength); + + int minRequired = indent + escapedValue.Length + 2; // 2 quotes + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteStringValue(escapedValue, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringEscapeValue(ReadOnlySpan utf8Value, int firstEscapeIndexVal) @@ -218,21 +293,10 @@ namespace System.Text.Json int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedValue = new Span(ptr, length); - } - } + Span escapedValue = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (valueArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); WriteStringByOptions(escapedValue.Slice(0, written)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs index 107bc0a..7659709 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -36,7 +38,7 @@ namespace System.Text.Json public void WriteNumberValue(ulong value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -51,21 +53,55 @@ namespace System.Text.Json private void WriteNumberValueMinimized(ulong value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatUInt64Length + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(ulong value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatUInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs index a328f25..cdb4a99 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs @@ -4,57 +4,47 @@ using System.Buffers; using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace System.Text.Json { /// /// Provides a high-performance API for forward-only, non-cached writing of UTF-8 encoded JSON text. + /// + /// /// It writes the text sequentially with no caching and adheres to the JSON RFC /// by default (https://tools.ietf.org/html/rfc8259), with the exception of writing comments. - /// + /// /// /// When the user attempts to write invalid JSON and validation is enabled, it throws - /// a with a context specific error message. - /// Since this type is a ref struct, it does not directly support async. However, it does provide - /// support for reentrancy to write partial data, and continue writing in chunks. + /// an with a context specific error message. + /// + /// /// To be able to format the output with indentation and whitespace OR to skip validation, create an instance of - /// and pass that in to the writer. + /// and pass that in to the writer. /// - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter : IDisposable +#if BUILDING_INBOX_LIBRARY + , IAsyncDisposable +#endif { - private const int StackallocThreshold = 256; - private const int DefaultGrowthSize = 4096; + // Depending on OS, either '\r\n' OR '\n' + private static readonly int s_newLineLength = Environment.NewLine.Length; - private readonly IBufferWriter _output; - private int _buffered; - private Span _buffer; + private const int DefaultGrowthSize = 4096; - /// - /// Returns the total amount of bytes written by the so far - /// for the current instance of the . - /// This includes data that has been written beyond what has already been committed. - /// - public long BytesWritten - { - get - { - Debug.Assert(BytesCommitted <= long.MaxValue - _buffered); - return BytesCommitted + _buffered; - } - } + private IBufferWriter _output; + private Stream _stream; + private ArrayBufferWriter _arrayBufferWriter; - /// - /// Returns the total amount of bytes committed to the output by the so far - /// for the current instance of the . - /// This is how much the IBufferWriter has advanced. - /// - public long BytesCommitted { get; private set; } + private Memory _memory; private bool _inObject; private bool _isNotPrimitive; private JsonTokenType _tokenType; - private readonly JsonWriterOptions _writerOptions; private BitStack _bitStack; // The highest order bit of _currentDepth is used to discern whether we are writing the first item in a list or not. @@ -62,6 +52,28 @@ namespace System.Text.Json // else, no list separator is needed since we are writing the first item. private int _currentDepth; + /// + /// Returns the amount of bytes written by the so far + /// that have not yet been flushed to the output and committed. + /// + public int BytesPending { get; private set; } + + /// + /// Returns the amount of bytes committed to the output by the so far. + /// + /// + /// In the case of IBufferwriter, this is how much the IBufferWriter has advanced. + /// In the case of Stream, this is how much data has been written to the stream. + /// + public long BytesCommitted { get; private set; } + + /// + /// Gets the custom behavior when writing JSON using + /// the which indicates whether to format the output + /// while writing and whether to skip structural JSON validation or not. + /// + public JsonWriterOptions Options { get; } + private int Indentation => CurrentDepth * JsonConstants.SpacesPerIndent; /// @@ -71,99 +83,308 @@ namespace System.Text.Json public int CurrentDepth => _currentDepth & JsonConstants.RemoveFlagsBitMask; /// - /// Returns the current snapshot of the state which must - /// be captured by the caller and passed back in to the ctor with more data. + /// Constructs a new instance with a specified . /// - /// - /// Thrown when there is JSON data that has been written and buffered but not yet flushed to the . - /// Getting the state for creating a new without first committing the data that has been written - /// would result in an inconsistent state. Call Flush before getting the current state. + /// An instance of used as a destination for writing JSON text into. + /// Defines the customized behavior of the + /// By default, the writes JSON minimized (i.e. with no extra whitespace) + /// and validates that the JSON being written is structurally valid according to JSON RFC. + /// + /// Thrown when the instance of that is passed in is null. + /// + public Utf8JsonWriter(IBufferWriter bufferWriter, JsonWriterOptions options = default) + { + _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); + _stream = default; + _arrayBufferWriter = default; + + BytesPending = default; + BytesCommitted = default; + _memory = default; + + _inObject = default; + _isNotPrimitive = default; + _tokenType = default; + _currentDepth = default; + Options = options; + + // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. + // This way we avoid allocations in the common, default cases, and allocate lazily. + _bitStack = default; + } + + /// + /// Constructs a new instance with a specified . + /// + /// An instance of used as a destination for writing JSON text into. + /// Defines the customized behavior of the + /// By default, the writes JSON minimized (i.e. with no extra whitespace) + /// and validates that the JSON being written is structurally valid according to JSON RFC. + /// + /// Thrown when the instance of that is passed in is null. /// + public Utf8JsonWriter(Stream utf8Json, JsonWriterOptions options = default) + { + if (utf8Json == null) + throw new ArgumentNullException(nameof(utf8Json)); + if (!utf8Json.CanWrite) + throw new ArgumentException(SR.StreamNotWritable); + + _stream = utf8Json; + _arrayBufferWriter = new ArrayBufferWriter(); + _output = _arrayBufferWriter; + + BytesPending = default; + BytesCommitted = default; + _memory = default; + + _inObject = default; + _isNotPrimitive = default; + _tokenType = default; + _currentDepth = default; + Options = options; + + // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. + // This way we avoid allocations in the common, default cases, and allocate lazily. + _bitStack = default; + } + + /// + /// Resets the internal state so that it can be re-used. + /// /// - /// Unlike the , which is a ref struct, the state can survive - /// across async/await boundaries and hence this type is required to provide support for reading - /// in more data asynchronously before continuing with a new instance of the . + /// The will continue to use the original writer options + /// and the original output as the destination (either or ). /// - public JsonWriterState GetCurrentState() + public void Reset() { - if (_buffered != 0) + if (_arrayBufferWriter != null) { - throw ThrowHelper.GetInvalidOperationException_CallFlushFirst(_buffered); + _arrayBufferWriter.Clear(); } - return new JsonWriterState + ResetHelper(); + } + + /// + /// Resets the internal state so that it can be re-used with the new instance of . + /// + /// An instance of used as a destination for writing JSON text into. + /// + /// The will continue to use the original writer options + /// but now write to the passed in as the new destination. + /// + /// + /// Thrown when the instance of that is passed in is null. + /// + /// + /// The instance of has been disposed. + /// + public void Reset(Stream utf8Json) + { + CheckNotDisposed(); + + if (utf8Json == null) + throw new ArgumentNullException(nameof(utf8Json)); + if (!utf8Json.CanWrite) + throw new ArgumentException(SR.StreamNotWritable); + + _stream = utf8Json; + if (_arrayBufferWriter == null) + { + _arrayBufferWriter = new ArrayBufferWriter(); + } + else { - _bytesWritten = BytesWritten, - _bytesCommitted = BytesCommitted, - _inObject = _inObject, - _isNotPrimitive = _isNotPrimitive, - _tokenType = _tokenType, - _currentDepth = _currentDepth, - _writerOptions = _writerOptions, - _bitStack = _bitStack, - }; + _arrayBufferWriter.Clear(); + } + _output = _arrayBufferWriter; + + ResetHelper(); } /// - /// Constructs a new instance with a specified . + /// Resets the internal state so that it can be re-used with the new instance of . /// /// An instance of used as a destination for writing JSON text into. - /// If this is the first call to the ctor, pass in a default state. Otherwise, - /// capture the state from the previous instance of the and pass that back. + /// + /// The will continue to use the original writer options + /// but now write to the passed in as the new destination. + /// /// /// Thrown when the instance of that is passed in is null. /// - /// - /// Since this type is a ref struct, it is a stack-only type and all the limitations of ref structs apply to it. - /// This is the reason why the ctor accepts a . - /// - public Utf8JsonWriter(IBufferWriter bufferWriter, JsonWriterState state = default) + /// + /// The instance of has been disposed. + /// + public void Reset(IBufferWriter bufferWriter) { + CheckNotDisposed(); + _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); - _buffered = 0; - BytesCommitted = 0; - _buffer = _output.GetSpan(); + _stream = default; + + if (_arrayBufferWriter != null) + { + _arrayBufferWriter.Clear(); + } - _inObject = state._inObject; - _isNotPrimitive = state._isNotPrimitive; - _tokenType = state._tokenType; - _writerOptions = state._writerOptions; - _bitStack = state._bitStack; + _arrayBufferWriter = default; - _currentDepth = state._currentDepth; + ResetHelper(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Advance(int count) + private void ResetHelper() { - Debug.Assert(count >= 0 && _buffered <= int.MaxValue - count); + BytesPending = default; + BytesCommitted = default; + _memory = default; + + _inObject = default; + _isNotPrimitive = default; + _tokenType = default; + _currentDepth = default; + + // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. + // This way we avoid allocations in the common, default cases, and allocate lazily. + _bitStack = default; + } - _buffered += count; - _buffer = _buffer.Slice(count); + private void CheckNotDisposed() + { + if (_output == null) + { + throw new ObjectDisposedException(nameof(Utf8JsonWriter)); + } } /// - /// Advances the underlying based on what has been written so far. + /// Commits the JSON text written so far which makes it visible to the output destination. /// - /// Let's the writer know whether more data will be written. This is used to validate - /// that the JSON written so far is structurally valid if no more data is to follow. - /// - /// Thrown when incomplete JSON has been written and is true. - /// (for example when an open object or array needs to be closed). - /// - public void Flush(bool isFinalBlock = true) + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it. + /// + public void Flush() { - if (isFinalBlock && !_writerOptions.SkipValidation && (CurrentDepth != 0 || _tokenType == JsonTokenType.None)) - ThrowHelper.ThrowInvalidOperationException_DepthNonZeroOrEmptyJson(_currentDepth); + FlushHelper(); + + if (_stream != null) + { + FlushHelperStream(); + _stream.Flush(); + } + + _memory = default; + BytesCommitted += BytesPending; + BytesPending = 0; + } + + /// + /// Commits any left over JSON text that has not yet been flushed and releases all resources used by the current instance. + /// + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it. + /// + /// + /// The instance cannot be re-used after disposing. + /// + public void Dispose() + { + if (_output == null) + { + return; + } Flush(); + ResetHelper(); + + _stream = null; + _arrayBufferWriter = null; + _output = null; + } + +#if BUILDING_INBOX_LIBRARY + /// + /// Asynchronously commits any left over JSON text that has not yet been flushed and releases all resources used by the current instance. + /// + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it. + /// + /// + /// The instance cannot be re-used after disposing. + /// + public async ValueTask DisposeAsync() + { + if (_output == null) + { + return; + } + + await FlushAsync().ConfigureAwait(false); + ResetHelper(); + + _stream = null; + _arrayBufferWriter = null; + _output = null; + } +#endif + + /// + /// Asynchronously commits the JSON text written so far which makes it visible to the output destination. + /// + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it asynchronously, while monitoring cancellation requests. + /// + public async Task FlushAsync(CancellationToken cancellationToken = default) + { + FlushHelper(); + + if (_stream != null) + { + Debug.Assert(_arrayBufferWriter != null); + Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount); + if (BytesPending != 0) + { +#if BUILDING_INBOX_LIBRARY + await _stream.WriteAsync(_arrayBufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false); +#else + Debug.Assert(_arrayBufferWriter.WrittenMemory.Length == _arrayBufferWriter.WrittenCount); + await _stream.WriteAsync(_arrayBufferWriter.WrittenMemory.ToArray(), 0, _arrayBufferWriter.WrittenCount, cancellationToken).ConfigureAwait(false); +#endif + _arrayBufferWriter.Clear(); + } + await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + _memory = default; + BytesCommitted += BytesPending; + BytesPending = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FlushHelperStream() + { + Debug.Assert(_arrayBufferWriter != null); + Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount); + if (BytesPending != 0) + { +#if BUILDING_INBOX_LIBRARY + _stream.Write(_arrayBufferWriter.WrittenSpan); +#else + Debug.Assert(_arrayBufferWriter.WrittenSpan.Length == _arrayBufferWriter.WrittenCount); + _stream.Write(_arrayBufferWriter.WrittenSpan.ToArray(), 0, _arrayBufferWriter.WrittenCount); +#endif + _arrayBufferWriter.Clear(); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Flush() + private void FlushHelper() { - _output.Advance(_buffered); - BytesCommitted += _buffered; - _buffered = 0; + _output.Advance(BytesPending); } /// @@ -197,7 +418,7 @@ namespace System.Text.Json if (CurrentDepth >= JsonConstants.MaxWriterDepth) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.DepthTooLarge, _currentDepth, token: default, tokenType: default); - if (_writerOptions.IndentedOrNotSkipValidation) + if (Options.IndentedOrNotSkipValidation) { WriteStartSlow(token); } @@ -213,32 +434,26 @@ namespace System.Text.Json private void WriteStartMinimized(byte token) { - int idx = 0; - if (_currentDepth < 0) + if (_memory.Length - BytesPending < 2) // 1 start token, and optionally, 1 list separator { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; + Grow(2); } - if (_buffer.Length <= idx) + Span output = _memory.Span; + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = token; - - Advance(idx); + output[BytesPending++] = token; } private void WriteStartSlow(byte token) { - Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation); + Debug.Assert(Options.Indented || !Options.SkipValidation); - if (_writerOptions.Indented) + if (Options.Indented) { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { ValidateStart(); UpdateBitStackOnStart(token); @@ -247,7 +462,7 @@ namespace System.Text.Json } else { - Debug.Assert(!_writerOptions.SkipValidation); + Debug.Assert(!Options.SkipValidation); ValidateStart(); UpdateBitStackOnStart(token); WriteStartMinimized(token); @@ -273,46 +488,42 @@ namespace System.Text.Json private void WriteStartIndented(byte token) { - int idx = 0; - if (_currentDepth < 0) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int minRequired = indent + 1; // 1 start token + int maxRequired = minRequired + 3; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; + Grow(maxRequired); } - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + Span output = _memory.Span; - int indent = Indentation; - while (true) + if (_currentDepth < 0) { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = token; - Advance(idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = token; } /// /// Writes the beginning of a JSON array with a property name as the key. /// /// The UTF-8 encoded property name of the JSON array to be written. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -320,18 +531,11 @@ namespace System.Text.Json /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartArray(ReadOnlySpan utf8PropertyName, bool escape = true) + public void WriteStartArray(ReadOnlySpan utf8PropertyName) { ValidatePropertyNameAndDepth(utf8PropertyName); - if (escape) - { - WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket); - } - else - { - WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBracket); - } + WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -343,7 +547,9 @@ namespace System.Text.Json /// Writes the beginning of a JSON object with a property name as the key. /// /// The UTF-8 encoded property name of the JSON object to be written. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -351,18 +557,11 @@ namespace System.Text.Json /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartObject(ReadOnlySpan utf8PropertyName, bool escape = true) + public void WriteStartObject(ReadOnlySpan utf8PropertyName) { ValidatePropertyNameAndDepth(utf8PropertyName); - if (escape) - { - WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace); - } - else - { - WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBrace); - } + WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -374,7 +573,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -389,24 +588,15 @@ namespace System.Text.Json private void WriteStartByOptions(ReadOnlySpan utf8PropertyName, byte token) { ValidateWritingProperty(token); - int idx; - if (_writerOptions.Indented) + + if (Options.Indented) { - idx = WritePropertyNameIndented(utf8PropertyName); + WritePropertyNameIndented(utf8PropertyName, token); } else { - idx = WritePropertyNameMinimized(utf8PropertyName); - } - - if (1 > _buffer.Length - idx) - { - AdvanceAndGrow(ref idx, 1); + WritePropertyNameMinimized(utf8PropertyName, token); } - - _buffer[idx++] = token; - - Advance(idx); } private void WriteStartEscapeProperty(ReadOnlySpan utf8PropertyName, byte token, int firstEscapeIndexProp) @@ -417,21 +607,10 @@ namespace System.Text.Json byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); @@ -447,7 +626,9 @@ namespace System.Text.Json /// Writes the beginning of a JSON array with a property name as the key. /// /// The UTF-16 encoded property name of the JSON array to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -455,14 +636,16 @@ namespace System.Text.Json /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartArray(string propertyName, bool escape = true) - => WriteStartArray(propertyName.AsSpan(), escape); + public void WriteStartArray(string propertyName) + => WriteStartArray(propertyName.AsSpan()); /// /// Writes the beginning of a JSON object with a property name as the key. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -470,14 +653,16 @@ namespace System.Text.Json /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartObject(string propertyName, bool escape = true) - => WriteStartObject(propertyName.AsSpan(), escape); + public void WriteStartObject(string propertyName) + => WriteStartObject(propertyName.AsSpan()); /// /// Writes the beginning of a JSON array with a property name as the key. /// /// The UTF-16 encoded property name of the JSON array to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -485,18 +670,11 @@ namespace System.Text.Json /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartArray(ReadOnlySpan propertyName, bool escape = true) + public void WriteStartArray(ReadOnlySpan propertyName) { ValidatePropertyNameAndDepth(propertyName); - if (escape) - { - WriteStartEscape(propertyName, JsonConstants.OpenBracket); - } - else - { - WriteStartByOptions(propertyName, JsonConstants.OpenBracket); - } + WriteStartEscape(propertyName, JsonConstants.OpenBracket); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -508,7 +686,9 @@ namespace System.Text.Json /// Writes the beginning of a JSON object with a property name as the key. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -516,18 +696,11 @@ namespace System.Text.Json /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartObject(ReadOnlySpan propertyName, bool escape = true) + public void WriteStartObject(ReadOnlySpan propertyName) { ValidatePropertyNameAndDepth(propertyName); - if (escape) - { - WriteStartEscape(propertyName, JsonConstants.OpenBrace); - } - else - { - WriteStartByOptions(propertyName, JsonConstants.OpenBrace); - } + WriteStartEscape(propertyName, JsonConstants.OpenBrace); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -539,7 +712,7 @@ namespace System.Text.Json { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -554,24 +727,15 @@ namespace System.Text.Json private void WriteStartByOptions(ReadOnlySpan propertyName, byte token) { ValidateWritingProperty(token); - int idx; - if (_writerOptions.Indented) + + if (Options.Indented) { - idx = WritePropertyNameIndented(propertyName); + WritePropertyNameIndented(propertyName, token); } else { - idx = WritePropertyNameMinimized(propertyName); - } - - if (1 > _buffer.Length - idx) - { - AdvanceAndGrow(ref idx, 1); + WritePropertyNameMinimized(propertyName, token); } - - _buffer[idx++] = token; - - Advance(idx); } private void WriteStartEscapeProperty(ReadOnlySpan propertyName, byte token, int firstEscapeIndexProp) @@ -582,21 +746,11 @@ namespace System.Text.Json char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStartByOptions(escapedPropertyName.Slice(0, written), token); @@ -633,7 +787,7 @@ namespace System.Text.Json private void WriteEnd(byte token) { - if (_writerOptions.IndentedOrNotSkipValidation) + if (Options.IndentedOrNotSkipValidation) { WriteEndSlow(token); } @@ -652,22 +806,22 @@ namespace System.Text.Json private void WriteEndMinimized(byte token) { - if (_buffer.Length < 1) + if (_memory.Length - BytesPending < 1) // 1 end token { - GrowAndEnsure(); + Grow(1); } - _buffer[0] = token; - Advance(1); + Span output = _memory.Span; + output[BytesPending++] = token; } private void WriteEndSlow(byte token) { - Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation); + Debug.Assert(Options.Indented || !Options.SkipValidation); - if (_writerOptions.Indented) + if (Options.Indented) { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { ValidateEnd(token); } @@ -675,7 +829,7 @@ namespace System.Text.Json } else { - Debug.Assert(!_writerOptions.SkipValidation); + Debug.Assert(!Options.SkipValidation); ValidateEnd(token); WriteEndMinimized(token); } @@ -716,10 +870,8 @@ namespace System.Text.Json } else { - int idx = 0; - WriteNewLine(ref idx); - int indent = Indentation; + // Necessary if WriteEndX is called without a corresponding WriteStartX first. if (indent != 0) { @@ -727,46 +879,37 @@ namespace System.Text.Json // current depth yet, explicitly subtract here. indent -= JsonConstants.SpacesPerIndent; } - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } - if (_buffer.Length <= idx) + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.None); + + int maxRequired = indent + 3; // 1 end token, 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = token; - Advance(idx); + Span output = _memory.Span; + + WriteNewLine(output); + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = token; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNewLine(ref int idx) + private void WriteNewLine(Span output) { // Write '\r\n' OR '\n', depending on OS - if (Environment.NewLine.Length == 2) - { - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.CarriageReturn; - } - - if (_buffer.Length <= idx) + if (s_newLineLength == 2) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.CarriageReturn; } - _buffer[idx++] = JsonConstants.LineFeed; + output[BytesPending++] = JsonConstants.LineFeed; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -785,61 +928,43 @@ namespace System.Text.Json } } - private void GrowAndEnsure() + private void Grow(int requiredSize) { - Flush(); - int previousSpanLength = _buffer.Length; - Debug.Assert(previousSpanLength < DefaultGrowthSize); - _buffer = _output.GetSpan(DefaultGrowthSize); - if (_buffer.Length <= previousSpanLength) - { - ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetLargerSpan); - } - } + Debug.Assert(requiredSize > 0); - private void GrowAndEnsure(int minimumSize) - { - Flush(); - Debug.Assert(minimumSize < DefaultGrowthSize); - _buffer = _output.GetSpan(DefaultGrowthSize); - if (_buffer.Length < minimumSize) + if (_memory.Length == 0) { - ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetMinimumSizeSpan, minimumSize); + Debug.Assert(BytesPending == 0); + _memory = _output.GetMemory(); + if (_memory.Length >= requiredSize) + { + return; + } } - } - private void AdvanceAndGrow(ref int alreadyWritten) - { - Debug.Assert(alreadyWritten >= 0); - Advance(alreadyWritten); - GrowAndEnsure(); - alreadyWritten = 0; - } + FlushHelper(); - private void AdvanceAndGrow(ref int alreadyWritten, int minimumSize) - { - Debug.Assert(minimumSize >= 1 && minimumSize <= 128); - Advance(alreadyWritten); - GrowAndEnsure(minimumSize); - alreadyWritten = 0; - } + int sizeHint = Math.Max(DefaultGrowthSize, requiredSize); - private void CopyLoop(ReadOnlySpan span, ref int idx) - { - while (true) + if (_stream != null) { - if (span.Length <= _buffer.Length - idx) + FlushHelperStream(); + _memory = _output.GetMemory(sizeHint); + + Debug.Assert(_memory.Length >= sizeHint); + } + else + { + _memory = _output.GetMemory(sizeHint); + + if (_memory.Length < sizeHint) { - span.CopyTo(_buffer.Slice(idx)); - idx += span.Length; - break; + ThrowHelper.ThrowInvalidOperationException_NeedLargerSpan(); } - - span.Slice(0, _buffer.Length - idx).CopyTo(_buffer.Slice(idx)); - span = span.Slice(_buffer.Length - idx); - idx = _buffer.Length; - AdvanceAndGrow(ref idx); } + + BytesCommitted += BytesPending; + BytesPending = 0; } private void SetFlagToAddListSeparatorBeforeNextItem() diff --git a/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs b/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs deleted file mode 100644 index 13249f8..0000000 --- a/src/libraries/System.Text.Json/tests/ArrayBufferWriter.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Tests -{ - internal class ArrayBufferWriter : IBufferWriter, IDisposable - { - private ResizableArray _buffer; - - public ArrayBufferWriter(int capacity) - { - _buffer = new ResizableArray(ArrayPool.Shared.Rent(capacity)); - } - - public int CommitedByteCount => _buffer.Count; - - public void Clear() - { - _buffer.Count = 0; - } - - public ArraySegment Free => _buffer.Free; - - public ArraySegment Formatted => _buffer.Full; - - public Memory GetMemory(int minimumLength = 0) - { - if (minimumLength < 1) - { - minimumLength = 1; - } - - if (minimumLength > _buffer.FreeCount) - { - int doubleCount = _buffer.FreeCount * 2; - int newSize = minimumLength > doubleCount ? minimumLength : doubleCount; - byte[] newArray = ArrayPool.Shared.Rent(newSize + _buffer.Count); - byte[] oldArray = _buffer.Resize(newArray); - ArrayPool.Shared.Return(oldArray); - } - - return _buffer.FreeMemory; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(int minimumLength = 0) - { - if (minimumLength < 1) - { - minimumLength = 1; - } - - if (minimumLength > _buffer.FreeCount) - { - int doubleCount = _buffer.FreeCount * 2; - int newSize = minimumLength > doubleCount ? minimumLength : doubleCount; - byte[] newArray = ArrayPool.Shared.Rent(newSize + _buffer.Count); - byte[] oldArray = _buffer.Resize(newArray); - ArrayPool.Shared.Return(oldArray); - } - - return _buffer.FreeSpan; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int bytes) - { - _buffer.Count += bytes; - if (_buffer.Count > _buffer.Capacity) - { - throw new InvalidOperationException("More bytes commited than returned from FreeBuffer"); - } - } - - public void Dispose() - { - byte[] array = _buffer.Array; - _buffer.Array = null; - ArrayPool.Shared.Return(array); - } - } -} diff --git a/src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs b/src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs index 79e7c34..b22e796 100644 --- a/src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs +++ b/src/libraries/System.Text.Json/tests/FixedSizedBufferWriter.cs @@ -26,6 +26,8 @@ namespace System.Text.Json.Tests public byte[] Formatted => _buffer.AsSpan(0, _count).ToArray(); + public int FormattedCount => _count; + public Memory GetMemory(int minimumLength = 0) => _buffer.AsMemory(_count); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -34,11 +36,11 @@ namespace System.Text.Json.Tests [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int bytes) { - _count += bytes; - if (_count > _buffer.Length) + if (_count > _buffer.Length - bytes) { throw new InvalidOperationException("Cannot advance past the end of the buffer."); } + _count += bytes; } } } diff --git a/src/libraries/System.Text.Json/tests/JsonDocumentTests.cs b/src/libraries/System.Text.Json/tests/JsonDocumentTests.cs index b059d05..407f5ef 100644 --- a/src/libraries/System.Text.Json/tests/JsonDocumentTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonDocumentTests.cs @@ -1373,19 +1373,19 @@ namespace System.Text.Json.Tests Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsValue(ref writer); + root.WriteAsValue(writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); } } @@ -1418,19 +1418,19 @@ namespace System.Text.Json.Tests Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsValue(ref writer); + root.WriteAsValue(writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); } diff --git a/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs b/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs index ec0a2a3..70e2008 100644 --- a/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs +++ b/src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Xunit; +using System.Buffers; namespace System.Text.Json.Tests { @@ -138,7 +139,7 @@ namespace System.Text.Json.Tests { WriteSimpleValue(indented, "false"); } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -391,6 +392,26 @@ null, [Theory] [InlineData(false)] [InlineData(true)] + public static void WriteNumberAsPropertyWithLargeName(bool indented) + { + var charArray = new char[300]; + charArray.AsSpan().Fill('a'); + charArray[0] = (char)0xEA; + var propertyName = new string(charArray); + + WritePropertyValueBothForms( + indented, + propertyName, + "42", + @"{ + ""\u00ea" + propertyName.Substring(1) + @""": 42 +}", + $"{{\"\\u00ea{propertyName.Substring(1)}\":42}}"); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] public static void WriteNumberScientificAsProperty(bool indented) { WritePropertyValueBothForms( @@ -806,15 +827,14 @@ null, closeBrackets.Fill((byte)']'); jsonIn.AsSpan(SpacesPre + TargetDepth + SpacesSplit + TargetDepth).Fill((byte)' '); - using (ArrayBufferWriter buffer = new ArrayBufferWriter(jsonIn.Length)) + var buffer = new ArrayBufferWriter(jsonIn.Length); using (JsonDocument doc = JsonDocument.Parse(jsonIn, optionsCopy)) { var writer = new Utf8JsonWriter(buffer); - doc.RootElement.WriteAsValue(ref writer); + doc.RootElement.WriteAsValue(writer); writer.Flush(); - ArraySegment formattedSegment = buffer.Formatted; - ReadOnlySpan formatted = formattedSegment; + ReadOnlySpan formatted = buffer.WrittenSpan; Assert.Equal(TargetDepth + TargetDepth, formatted.Length); Assert.True(formatted.Slice(0, TargetDepth).SequenceEqual(openBrackets), "OpenBrackets match"); @@ -827,26 +847,25 @@ null, [InlineData(true)] public static void WritePropertyOutsideObject(bool skipValidation) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (var doc = JsonDocument.Parse("[ null, false, true, \"hi\", 5, {}, [] ]", s_readerOptions)) { JsonElement root = doc.RootElement; - JsonWriterState state = new JsonWriterState( - new JsonWriterOptions - { - SkipValidation = skipValidation, - }); + var options = new JsonWriterOptions + { + SkipValidation = skipValidation, + }; const string CharLabel = "char"; byte[] byteUtf8 = Encoding.UTF8.GetBytes("byte"); - Utf8JsonWriter writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); if (skipValidation) { foreach (JsonElement val in root.EnumerateArray()) { - val.WriteAsProperty(CharLabel.AsSpan(), ref writer); - val.WriteAsProperty(byteUtf8, ref writer); + val.WriteAsProperty(CharLabel.AsSpan(), writer); + val.WriteAsProperty(byteUtf8, writer); } writer.Flush(); @@ -867,18 +886,14 @@ null, { JsonTestHelper.AssertThrows( ref writer, - (ref Utf8JsonWriter w) => val.WriteAsProperty(CharLabel.AsSpan(), ref w)); + (ref Utf8JsonWriter w) => val.WriteAsProperty(CharLabel.AsSpan(), w)); JsonTestHelper.AssertThrows( ref writer, - (ref Utf8JsonWriter w) => val.WriteAsProperty(byteUtf8, ref w)); + (ref Utf8JsonWriter w) => val.WriteAsProperty(byteUtf8, w)); } - JsonTestHelper.AssertThrows( - ref writer, - (ref Utf8JsonWriter w) => w.Flush()); - - writer.Flush(isFinalBlock: false); + writer.Flush(); AssertContents("", buffer); } @@ -890,24 +905,23 @@ null, [InlineData(true)] public static void WriteValueInsideObject(bool skipValidation) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (var doc = JsonDocument.Parse("[ null, false, true, \"hi\", 5, {}, [] ]", s_readerOptions)) { JsonElement root = doc.RootElement; - JsonWriterState state = new JsonWriterState( - new JsonWriterOptions - { - SkipValidation = skipValidation, - }); + var options = new JsonWriterOptions + { + SkipValidation = skipValidation, + }; - Utf8JsonWriter writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); writer.WriteStartObject(); if (skipValidation) { foreach (JsonElement val in root.EnumerateArray()) { - val.WriteAsValue(ref writer); + val.WriteAsValue(writer); } writer.WriteEndObject(); @@ -923,7 +937,7 @@ null, { JsonTestHelper.AssertThrows( ref writer, - (ref Utf8JsonWriter w) => val.WriteAsValue(ref w)); + (ref Utf8JsonWriter w) => val.WriteAsValue(w)); } writer.WriteEndObject(); @@ -936,20 +950,19 @@ null, private static void WriteSimpleValue(bool indented, string jsonIn, string jsonOut = null) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); - target.WriteAsValue(ref writer); + target.WriteAsValue(writer); writer.Flush(); AssertContents(jsonOut ?? jsonIn, buffer); @@ -962,20 +975,19 @@ null, string expectedIndent, string expectedMinimal) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); - target.WriteAsValue(ref writer); + target.WriteAsValue(writer); writer.Flush(); if (indented && s_replaceNewlines) @@ -1018,21 +1030,20 @@ null, string expectedIndent, string expectedMinimal) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); writer.WriteStartObject(); - target.WriteAsProperty(propertyName, ref writer); + target.WriteAsProperty(propertyName, writer); writer.WriteEndObject(); writer.Flush(); @@ -1054,21 +1065,20 @@ null, string expectedIndent, string expectedMinimal) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); writer.WriteStartObject(); - target.WriteAsProperty(propertyName, ref writer); + target.WriteAsProperty(propertyName, writer); writer.WriteEndObject(); writer.Flush(); @@ -1083,14 +1093,14 @@ null, } } - private static void AssertContents(string expectedValue, ArrayBufferWriter buffer) + private static void AssertContents(string expectedValue, ArrayBufferWriter buffer) { Assert.Equal( expectedValue, Encoding.UTF8.GetString( - buffer.Formatted + buffer.WrittenSpan #if netstandard - .AsSpan().ToArray() + .ToArray() #endif )); } diff --git a/src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs b/src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs new file mode 100644 index 0000000..4184481 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/JsonWriterOptionsTests.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Text.Json.Tests +{ + public static partial class JsonWriterOptionsTests + { + [Fact] + public static void JsonWriterOptionsDefaultCtor() + { + JsonWriterOptions options = default; + + var expectedOption = new JsonWriterOptions + { + Indented = false, + SkipValidation = false + }; + Assert.Equal(expectedOption, options); + } + + [Fact] + public static void JsonWriterOptionsCtor() + { + var options = new JsonWriterOptions(); + + var expectedOption = new JsonWriterOptions + { + Indented = false, + SkipValidation = false + }; + Assert.Equal(expectedOption, options); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public static void JsonWriterOptions(bool indented, bool skipValidation) + { + var options = new JsonWriterOptions(); + options.Indented = indented; + options.SkipValidation = skipValidation; + + var expectedOption = new JsonWriterOptions + { + Indented = indented, + SkipValidation = skipValidation + }; + Assert.Equal(expectedOption, options); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs b/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs deleted file mode 100644 index 87a1670..0000000 --- a/src/libraries/System.Text.Json/tests/JsonWriterStateTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Xunit; - -namespace System.Text.Json.Tests -{ - public static partial class JsonWriterStateTests - { - [Fact] - public static void DefaultJsonWriterState() - { - JsonWriterState state = default; - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - var expectedOption = new JsonWriterOptions - { - Indented = false, - SkipValidation = false - }; - Assert.Equal(expectedOption, state.Options); - } - - [Fact] - public static void JsonWriterStateDefaultCtor() - { - var state = new JsonWriterState(); - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - var expectedOption = new JsonWriterOptions - { - Indented = false, - SkipValidation = false - }; - Assert.Equal(expectedOption, state.Options); - } - - [Fact] - public static void JsonWriterStateCtor() - { - var state = new JsonWriterState(options: default); - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - var expectedOption = new JsonWriterOptions - { - Indented = false, - SkipValidation = false - }; - Assert.Equal(expectedOption, state.Options); - } - } -} diff --git a/src/libraries/System.Text.Json/tests/ResizableArray.cs b/src/libraries/System.Text.Json/tests/ResizableArray.cs deleted file mode 100644 index b02d6b4..0000000 --- a/src/libraries/System.Text.Json/tests/ResizableArray.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Tests -{ - // a List like type designed to be embeded in other types - internal struct ResizableArray - { - public ResizableArray(T[] array, int count = 0) - { - Array = array; - Count = count; - } - - public T[] Array { get; set; } - - public int Count { get; set; } - - public int Capacity => Array.Length; - - public T[] Resize(T[] newArray) - { - T[] oldArray = Array; - Array.AsSpan(0, Count).CopyTo(newArray); // CopyTo will throw if newArray.Length < _count - Array = newArray; - return oldArray; - } - - public ArraySegment Full => new ArraySegment(Array, 0, Count); - - public ArraySegment Free => new ArraySegment(Array, Count, Array.Length - Count); - - public Span FreeSpan => new Span(Array, Count, Array.Length - Count); - - public Memory FreeMemory => new Memory(Array, Count, Array.Length - Count); - - public int FreeCount => Array.Length - Count; - } -} diff --git a/src/libraries/System.Text.Json/tests/Resources/Strings.resx b/src/libraries/System.Text.Json/tests/Resources/Strings.resx index 3249795..6133f4a 100644 --- a/src/libraries/System.Text.Json/tests/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/tests/Resources/Strings.resx @@ -20268,4 +20268,7 @@ tiline\"another\" String\\"],"str":"\"\""} } } + + Cannot advance past the end of the buffer, which has a size of {0}. + \ No newline at end of file diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index 42f3f71..55b32d1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -7,7 +7,6 @@ CommonTest\System\IO\WrappedMemoryStream.cs - @@ -21,8 +20,7 @@ - - + @@ -58,6 +56,11 @@ + + + CommonTest\System\Buffers\ArrayBufferWriter.cs + + diff --git a/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs b/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs index 32bf61c..0ce3e88 100644 --- a/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs +++ b/src/libraries/System.Text.Json/tests/Utf8JsonWriterTests.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using Xunit; +using System.Buffers; using System.IO; using Newtonsoft.Json; using System.Globalization; +using System.Threading.Tasks; namespace System.Text.Json.Tests { @@ -20,21 +22,261 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void NullCtor(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - try - { - var jsonUtf8 = new Utf8JsonWriter(null); - Assert.True(false, "Expected ArgumentNullException to be thrown when null IBufferWriter is passed in."); - } - catch (ArgumentNullException) { } + Assert.Throws(() => new Utf8JsonWriter((Stream)null)); + Assert.Throws(() => new Utf8JsonWriter((IBufferWriter)null)); + Assert.Throws(() => new Utf8JsonWriter((Stream)null, options)); + Assert.Throws(() => new Utf8JsonWriter((IBufferWriter)null, options)); + } - try - { - var jsonUtf8 = new Utf8JsonWriter(null, state); - Assert.True(false, "Expected ArgumentNullException to be thrown when null IBufferWriter is passed in."); - } - catch (ArgumentNullException) { } + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void CantWriteToNonWritableStream(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var stream = new MemoryStream(); + stream.Dispose(); + + Assert.Throws(() => new Utf8JsonWriter(stream)); + Assert.Throws(() => new Utf8JsonWriter(stream, options)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InitialState(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream, options); + Assert.Equal(0, writer.BytesCommitted); + Assert.Equal(0, writer.BytesPending); + Assert.Equal(0, writer.CurrentDepth); + Assert.Equal(formatted, writer.Options.Indented); + Assert.Equal(skipValidation, writer.Options.SkipValidation); + Assert.Equal(0, stream.Position); + + var output = new FixedSizedBufferWriter(0); + writer = new Utf8JsonWriter(output, options); + Assert.Equal(0, writer.BytesCommitted); + Assert.Equal(0, writer.BytesPending); + Assert.Equal(0, writer.CurrentDepth); + Assert.Equal(formatted, writer.Options.Indented); + Assert.Equal(skipValidation, writer.Options.SkipValidation); + Assert.Equal(0, output.FormattedCount); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void Reset(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + writeToStream.Reset(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(formatted, writeToStream.Options.Indented); + Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.True(stream.Position != 0); + + long previousWritten = stream.Position; + writeToStream.Flush(); + Assert.Equal(previousWritten, stream.Position); + + var output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + writeToIBW.Reset(); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(formatted, writeToIBW.Options.Indented); + Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.True(output.FormattedCount != 0); + + previousWritten = output.FormattedCount; + writeToIBW.Flush(); + Assert.Equal(previousWritten, output.FormattedCount); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void ResetWithSameOutput(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + writeToStream.Reset(stream); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(formatted, writeToStream.Options.Indented); + Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.True(stream.Position != 0); + + long previousWritten = stream.Position; + writeToStream.Flush(); + Assert.Equal(previousWritten, stream.Position); + + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.NotEqual(previousWritten, stream.Position); + Assert.Equal("11", Encoding.UTF8.GetString(stream.ToArray())); + + var output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + writeToIBW.Reset(output); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(formatted, writeToIBW.Options.Indented); + Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.True(output.FormattedCount != 0); + + previousWritten = output.FormattedCount; + writeToIBW.Flush(); + Assert.Equal(previousWritten, output.FormattedCount); + + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.NotEqual(previousWritten, output.FormattedCount); + Assert.Equal("11", Encoding.UTF8.GetString(output.Formatted)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void ResetChangeOutputMode(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + var output = new FixedSizedBufferWriter(32); + writeToStream.Reset(output); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(formatted, writeToStream.Options.Indented); + Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.True(stream.Position != 0); + + long previousWrittenStream = stream.Position; + long previousWrittenIBW = output.FormattedCount; + Assert.Equal(0, previousWrittenIBW); + writeToStream.Flush(); + Assert.Equal(previousWrittenStream, stream.Position); + Assert.Equal(previousWrittenIBW, output.FormattedCount); + + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + Assert.Equal(previousWrittenStream, stream.Position); + Assert.True(output.FormattedCount != 0); + + output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + stream = new MemoryStream(); + writeToIBW.Reset(stream); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(formatted, writeToIBW.Options.Indented); + Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.True(output.FormattedCount != 0); + + previousWrittenStream = stream.Position; + previousWrittenIBW = output.FormattedCount; + Assert.Equal(0, previousWrittenStream); + writeToIBW.Flush(); + Assert.Equal(previousWrittenStream, stream.Position); + Assert.Equal(previousWrittenIBW, output.FormattedCount); + + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + Assert.Equal(previousWrittenIBW, output.FormattedCount); + Assert.True(stream.Position != 0); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidReset(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + + Assert.Throws(() => writeToStream.Reset((Stream)null)); + Assert.Throws(() => writeToStream.Reset((IBufferWriter)null)); + + stream.Dispose(); + + Assert.Throws(() => writeToStream.Reset(stream)); + + var output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + + Assert.Throws(() => writeToIBW.Reset((Stream)null)); + Assert.Throws(() => writeToIBW.Reset((IBufferWriter)null)); + + Assert.Throws(() => writeToIBW.Reset(stream)); } [Theory] @@ -44,25 +286,41 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void FlushEmpty(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(0); - try - { - var jsonUtf8 = new Utf8JsonWriter(output, state); - jsonUtf8.Flush(); - WriterDidNotThrow(skipValidation, "Expected InvalidOperationException to be thrown when calling Flush on an empty JSON payload."); - } - catch (InvalidOperationException) { } - output = new FixedSizedBufferWriter(10); - try - { - var jsonUtf8 = new Utf8JsonWriter(output, state); - jsonUtf8.WriteCommentValue("hi"); - jsonUtf8.Flush(); - WriterDidNotThrow(skipValidation, "Expected InvalidOperationException to be thrown when calling Flush on an empty JSON payload."); - } - catch (InvalidOperationException) { } + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.Flush(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.Flush(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, stream.Position); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task FlushEmptyAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(0); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + await jsonUtf8.FlushAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + await writeToStream.FlushAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, stream.Position); } [Theory] @@ -72,16 +330,39 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void FlushMultipleTimes(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); jsonUtf8.Flush(); Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); jsonUtf8.Flush(); Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + writeToStream.Flush(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); + writeToStream.Flush(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); } [Theory] @@ -89,20 +370,275 @@ namespace System.Text.Json.Tests [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] - public void InvalidBufferWriter(bool formatted, bool skipValidation) + public async Task FlushMultipleTimesAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); + await jsonUtf8.FlushAsync(); + Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); + await jsonUtf8.FlushAsync(); + Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + await writeToStream.FlushAsync(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); + await writeToStream.FlushAsync(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void DisposeAutoFlushes(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + jsonUtf8.Dispose(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, stream.Position); + writeToStream.Dispose(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, stream.Position); + } + +#if !netstandard + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task DisposeAutoFlushesAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + await jsonUtf8.DisposeAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, stream.Position); + await writeToStream.DisposeAsync(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, stream.Position); + } +#endif + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void UseAfterDisposeInvalid(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(1, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); + jsonUtf8.Dispose(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(1, output.FormattedCount); + Assert.Throws(() => jsonUtf8.Flush()); + jsonUtf8.Dispose(); + Assert.Throws(() => jsonUtf8.Flush()); + + jsonUtf8.Reset(); + + var stream = new MemoryStream(); + Assert.Throws(() => jsonUtf8.Reset(stream)); + + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(1, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + writeToStream.Dispose(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(1, stream.Position); + Assert.Throws(() => writeToStream.Flush()); + writeToStream.Dispose(); + Assert.Throws(() => writeToStream.Flush()); + + writeToStream.Reset(); + + Assert.Throws(() => jsonUtf8.Reset(output)); + } + +#if !netstandard + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task UseAfterDisposeInvalidAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(1, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); + await jsonUtf8.DisposeAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(1, output.FormattedCount); + await Assert.ThrowsAsync(() => jsonUtf8.FlushAsync()); + await jsonUtf8.DisposeAsync(); + await Assert.ThrowsAsync(() => jsonUtf8.FlushAsync()); + + jsonUtf8.Reset(); + + var stream = new MemoryStream(); + Assert.Throws(() => jsonUtf8.Reset(stream)); + + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(1, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + await writeToStream.DisposeAsync(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(1, stream.Position); + await Assert.ThrowsAsync(() => writeToStream.FlushAsync()); + await writeToStream.DisposeAsync(); + await Assert.ThrowsAsync(() => writeToStream.FlushAsync()); + + writeToStream.Reset(); + + Assert.Throws(() => jsonUtf8.Reset(output)); + } +#endif + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidBufferWriter(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new InvalidBufferWriter(); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); - try + Assert.Throws(() => jsonUtf8.WriteNumberValue((ulong)12345678901)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task WriteLargeToStream(bool formatted, bool skipValidation) + { + var stream = new MemoryStream(); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + await WriteLargeToStreamHelper(stream, options); + + string expectedString = GetExpectedLargeString(formatted); + string actualString = Encoding.UTF8.GetString(stream.ToArray()); + + Assert.Equal(expectedString, actualString); + } + + private static async Task WriteLargeToStreamHelper(Stream stream, JsonWriterOptions options) + { + const int SyncWriteThreshold = 25_000; + +#if !netstandard + await +#endif + using var jsonUtf8 = new Utf8JsonWriter(stream, options); + + byte[] utf8String = Encoding.UTF8.GetBytes("some string 1234"); + + jsonUtf8.WriteStartArray(); + for (int i = 0; i < 10_000; i++) { - jsonUtf8.WriteNumberValue((ulong)12345678901); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); + jsonUtf8.WriteStringValue(utf8String); + if (jsonUtf8.BytesPending > SyncWriteThreshold) + { + await jsonUtf8.FlushAsync(); + } } - catch (ArgumentException) { } + jsonUtf8.WriteEndArray(); + } + + private static string GetExpectedLargeString(bool prettyPrint) + { + var ms = new MemoryStream(); + TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); + + var json = new JsonTextWriter(streamWriter) + { + Formatting = prettyPrint ? Formatting.Indented : Formatting.None + }; + + json.WriteStartArray(); + for (int i = 0; i < 10_000; i++) + { + json.WriteValue("some string 1234"); + } + json.WriteEnd(); + + json.Flush(); + + return Encoding.UTF8.GetString(ms.ToArray()); } [Theory] @@ -112,23 +648,16 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(37); - var jsonUtf8 = new Utf8JsonWriter(output, state); - + var jsonUtf8 = new Utf8JsonWriter(output, options); Guid guid = Guid.NewGuid(); - try - { - jsonUtf8.WriteStringValue(guid); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteStringValue(guid)); - output = new FixedSizedBufferWriter(39); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(41); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStringValue(guid); jsonUtf8.Flush(); string actualStr = Encoding.UTF8.GetString(output.Formatted); @@ -144,23 +673,16 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(20); - var jsonUtf8 = new Utf8JsonWriter(output, state); - + var jsonUtf8 = new Utf8JsonWriter(output, options); var date = new DateTime(2019, 1, 1); - try - { - jsonUtf8.WriteStringValue(date); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteStringValue(date)); - output = new FixedSizedBufferWriter(21); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(38); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStringValue(date); jsonUtf8.Flush(); string actualStr = Encoding.UTF8.GetString(output.Formatted); @@ -176,23 +698,16 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(26); - var jsonUtf8 = new Utf8JsonWriter(output, state); - + var jsonUtf8 = new Utf8JsonWriter(output, options); DateTimeOffset date = new DateTime(2019, 1, 1); - try - { - jsonUtf8.WriteStringValue(date); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteStringValue(date)); - output = new FixedSizedBufferWriter(27); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(38); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStringValue(date); jsonUtf8.Flush(); string actualStr = Encoding.UTF8.GetString(output.Formatted); @@ -208,14 +723,15 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var random = new Random(42); for (int i = 0; i < 1_000; i++) { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = JsonTestHelper.NextDecimal(random, 78E14, -78E14); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -227,10 +743,9 @@ namespace System.Text.Json.Tests for (int i = 0; i < 1_000; i++) { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = JsonTestHelper.NextDecimal(random, 1_000_000, -1_000_000); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -241,10 +756,9 @@ namespace System.Text.Json.Tests } { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = 9999999999999999999999999999m; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -255,10 +769,9 @@ namespace System.Text.Json.Tests } { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = -9999999999999999999999999999m; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -271,18 +784,12 @@ namespace System.Text.Json.Tests { var output = new FixedSizedBufferWriter(30); decimal value = -0.9999999999999999999999999999m; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); - try - { - jsonUtf8.WriteNumberValue(value); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteNumberValue(value)); - output = new FixedSizedBufferWriter(31); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(34); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -300,181 +807,256 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void InvalidJsonMismatch(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + var jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) { jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) { jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) + { + jsonUtf8.WriteStartArray("property at start"); + } + else { - jsonUtf8.WriteStartArray("property at start", escape: false); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartArray("property at start")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) { - jsonUtf8.WriteStartObject("property at start", escape: false); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartObject("property at start"); + } + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject("property at start")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray("property inside array", escape: false); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartArray("property inside array"); + } + else + { + Assert.Throws(() => jsonUtf8.WriteStartArray("property inside array")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + if (skipValidation) { jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); jsonUtf8.WriteStringValue("key"); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteStringValue("key")); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); jsonUtf8.WriteString("key", "value"); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteString("key", "value")); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteEndArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteEndArray(); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartObject("some object"); + jsonUtf8.WriteEndObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.WriteEndObject(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.WriteEndObject(); + jsonUtf8.WriteStartObject("some object"); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject("some object")); + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartArray("test array"); + jsonUtf8.WriteEndArray(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray("test array", escape: false); - jsonUtf8.WriteEndArray(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteEndArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteEndArray(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); - jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartObject("test object"); + if (skipValidation) { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject("test object", escape: false); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidJsonIncomplete(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteEndArray(); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartObject("some object"); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.True(jsonUtf8.CurrentDepth != 0); - output.Dispose(); + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartArray("test array"); + jsonUtf8.WriteEndArray(); + Assert.True(jsonUtf8.CurrentDepth != 0); } [Theory] @@ -482,160 +1064,98 @@ namespace System.Text.Json.Tests [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] - public void InvalidJsonIncomplete(bool formatted, bool skipValidation) + public void InvalidJsonPrimitive(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var output = new ArrayBufferWriter(1024); - - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteNumberValue(12345); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteStartObject(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteNumberValue(12345)); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteEndArray(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartArray()); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartObject(); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray("test array", escape: false); - jsonUtf8.WriteEndArray(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartObject()); } - catch (InvalidOperationException) { } - - output.Dispose(); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidJsonPrimitive(bool formatted, bool skipValidation) - { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteNumberValue(12345); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartArray("property name"); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartArray(); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartArray("property name")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartObject(); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartObject("property name"); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartArray("property name", escape: false); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartObject("property name")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartObject("property name", escape: false); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteString("property name", "value"); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteString("property name", "value", escape: false); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteString("property name", "value")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - output.Dispose(); + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } } [Theory] @@ -645,113 +1165,50 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void InvalidNumbersJson(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(double.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(double.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + var jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(double.NegativeInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(double.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(double.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(float.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(double.NaN)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(float.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(float.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(float.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(float.NegativeInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", double.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(float.NaN)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", double.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", double.NegativeInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", double.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", double.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", float.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", double.NaN)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", float.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", float.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", float.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", float.NegativeInfinity)); - output.Dispose(); + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", float.NaN)); } [Theory] @@ -759,21 +1216,19 @@ namespace System.Text.Json.Tests [InlineData(false)] public void InvalidJsonContinueShouldSucceed(bool formatted) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); for (int i = 0; i < 100; i++) + { jsonUtf8.WriteEndArray(); + } jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - var sb = new StringBuilder(); for (int i = 0; i < 100; i++) { @@ -786,9 +1241,7 @@ namespace System.Text.Json.Tests sb.Append(Environment.NewLine); sb.Append("[]"); - Assert.Equal(sb.ToString(), actualStr); - - output.Dispose(); + AssertContents(sb.ToString(), output); } [Theory] @@ -798,22 +1251,17 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WritingTooDeep(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + for (int i = 0; i < 1000; i++) { - for (int i = 0; i < 1001; i++) - { - jsonUtf8.WriteStartArray(); - } - Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000."); + jsonUtf8.WriteStartArray(); } - catch (InvalidOperationException) { } - - output.Dispose(); + Assert.Equal(1000, jsonUtf8.CurrentDepth); + Assert.Throws(() => jsonUtf8.WriteStartArray()); } [Theory] @@ -823,37 +1271,25 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WritingTooDeepProperty(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - try + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + for (int i = 0; i < 999; i++) { - jsonUtf8.WriteStartObject(); - for (int i = 0; i < 1000; i++) - { - jsonUtf8.WriteStartArray("name"); - } - Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000."); + jsonUtf8.WriteStartObject("name"); } - catch (InvalidOperationException) { } + Assert.Equal(1000, jsonUtf8.CurrentDepth); + Assert.Throws(() => jsonUtf8.WriteStartArray("name")); - jsonUtf8 = new Utf8JsonWriter(output, state); - - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + for (int i = 0; i < 999; i++) { - jsonUtf8.WriteStartObject(); - for (int i = 0; i < 1000; i++) - { - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("name")); - } - Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000."); + jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("name")); } - catch (InvalidOperationException) { } - - output.Dispose(); + Assert.Throws(() => jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("name"))); } [ConditionalTheory(nameof(IsX64))] @@ -864,14 +1300,8 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WritingTooLargeProperty(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); - - var jsonUtf8 = new Utf8JsonWriter(output, state); - - Span key; - Span keyChars; + byte[] key; + char[] keyChars; try { @@ -883,28 +1313,19 @@ namespace System.Text.Json.Tests return; } - key.Fill((byte)'a'); - keyChars.Fill('a'); - - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray(keyChars); - Assert.True(false, $"Expected ArgumentException for property too large wasn't thrown. PropertyLength: {keyChars.Length}"); - } - catch (ArgumentException) { } + key.AsSpan().Fill((byte)'a'); + keyChars.AsSpan().Fill('a'); - jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray(key); - Assert.True(false, $"Expected ArgumentException for property too large wasn't thrown. PropertyLength: {key.Length}"); - } - catch (ArgumentException) { } + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteStartArray(keyChars)); - output.Dispose(); + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteStartArray(key)); } [Theory] @@ -916,24 +1337,16 @@ namespace System.Text.Json.Tests { string expectedStr = "123456789012345"; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 3; i++) - { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - jsonUtf8.WriteNumberValue(123456789012345); - - jsonUtf8.Flush(); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + jsonUtf8.WriteNumberValue(123456789012345); - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); + jsonUtf8.Flush(); - output.Dispose(); - } + AssertContents(expectedStr, output); } [Theory] @@ -943,58 +1356,144 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WriteHelloWorld(bool formatted, bool skipValidation) { - string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted); + string propertyName = "message"; + string value = "Hello, World!"; + string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted, propertyName, value); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 9; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString("message", "Hello, World!", escape: false); + jsonUtf8.WriteString("message", "Hello, World!"); + jsonUtf8.WriteString("message", "Hello, World!"); break; case 1: - jsonUtf8.WriteString("message", "Hello, World!".AsSpan(), escape: false); + jsonUtf8.WriteString("message", "Hello, World!".AsSpan()); + jsonUtf8.WriteString("message", "Hello, World!".AsSpan()); break; case 2: - jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!"), escape: false); + jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!")); + jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!")); break; case 3: - jsonUtf8.WriteString("message".AsSpan(), "Hello, World!", escape: false); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!"); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!"); break; case 4: - jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan(), escape: false); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan()); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan()); break; case 5: - jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!"), escape: false); + jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!")); + jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!")); break; case 6: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!", escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!"); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!"); break; case 7: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan(), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan()); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan()); break; case 8: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!"), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!")); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!")); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + AssertContents(expectedStr, output); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteHelloWorldEscaped(bool formatted, bool skipValidation) + { + string propertyName = "mess> propertyNameSpan = propertyName.AsSpan(); + ReadOnlySpan valueSpan = value.AsSpan(); + ReadOnlySpan propertyNameSpanUtf8 = Encoding.UTF8.GetBytes(propertyName); + ReadOnlySpan valueSpanUtf8 = Encoding.UTF8.GetBytes(value); + + for (int i = 0; i < 9; i++) + { + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + jsonUtf8.WriteStartObject(); + + switch (i) + { + case 0: + jsonUtf8.WriteString(propertyName, value); + jsonUtf8.WriteString(propertyName, value); + break; + case 1: + jsonUtf8.WriteString(propertyName, valueSpan); + jsonUtf8.WriteString(propertyName, valueSpan); + break; + case 2: + jsonUtf8.WriteString(propertyName, valueSpanUtf8); + jsonUtf8.WriteString(propertyName, valueSpanUtf8); + break; + case 3: + jsonUtf8.WriteString(propertyNameSpan, value); + jsonUtf8.WriteString(propertyNameSpan, value); + break; + case 4: + jsonUtf8.WriteString(propertyNameSpan, valueSpan); + jsonUtf8.WriteString(propertyNameSpan, valueSpan); + break; + case 5: + jsonUtf8.WriteString(propertyNameSpan, valueSpanUtf8); + jsonUtf8.WriteString(propertyNameSpan, valueSpanUtf8); + break; + case 6: + jsonUtf8.WriteString(propertyNameSpanUtf8, value); + jsonUtf8.WriteString(propertyNameSpanUtf8, value); + break; + case 7: + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpan); + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpan); + break; + case 8: + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpanUtf8); + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpanUtf8); + break; + } - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); + jsonUtf8.WriteEndObject(); + jsonUtf8.Flush(); - output.Dispose(); + AssertContents(expectedStr, output); } + + // Verify that escaping does not change the input strings/spans. + Assert.Equal("mess>< World!", value); + Assert.True(propertyName.AsSpan().SequenceEqual(propertyNameSpan)); + Assert.True(value.AsSpan().SequenceEqual(valueSpan)); + Assert.True(Encoding.UTF8.GetBytes(propertyName).AsSpan().SequenceEqual(propertyNameSpanUtf8)); + Assert.True(Encoding.UTF8.GetBytes(value).AsSpan().SequenceEqual(valueSpanUtf8)); } [Theory] @@ -1004,32 +1503,32 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WritePartialHelloWorld(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); Assert.Equal(0, jsonUtf8.BytesCommitted); - Assert.Equal(1, jsonUtf8.BytesWritten); + Assert.Equal(1, jsonUtf8.BytesPending); jsonUtf8.WriteString("message", "Hello, World!"); - Assert.Equal(16, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesCommitted); if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesWritten); + Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else - Assert.Equal(26, jsonUtf8.BytesWritten); + Assert.Equal(26, jsonUtf8.BytesPending); - jsonUtf8.Flush(isFinalBlock: false); + jsonUtf8.Flush(); if (formatted) Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(26, jsonUtf8.BytesCommitted); - Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten); + Assert.Equal(0, jsonUtf8.BytesPending); jsonUtf8.WriteString("message", "Hello, World!"); jsonUtf8.WriteEndObject(); @@ -1040,83 +1539,18 @@ namespace System.Text.Json.Tests Assert.Equal(26, jsonUtf8.BytesCommitted); if (formatted) - Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesWritten); // new lines, indentation, white space + Assert.Equal(27 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else - Assert.Equal(53, jsonUtf8.BytesWritten); + Assert.Equal(27, jsonUtf8.BytesPending); - jsonUtf8.Flush(isFinalBlock: true); + jsonUtf8.Flush(); if (formatted) Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(53, jsonUtf8.BytesCommitted); - Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten); - - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - state = jsonUtf8.GetCurrentState(); - Assert.Equal(jsonUtf8.BytesCommitted, state.BytesCommitted); - Assert.Equal(jsonUtf8.BytesWritten, state.BytesWritten); - - output.Dispose(); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritePartialHelloWorldSaveState(bool formatted, bool skipValidation) - { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - Assert.Equal(0, jsonUtf8.CurrentDepth); - jsonUtf8.WriteStartObject(); - Assert.Equal(1, jsonUtf8.CurrentDepth); - jsonUtf8.Flush(isFinalBlock: false); - - state = jsonUtf8.GetCurrentState(); - - Assert.Equal(1, state.BytesCommitted); - Assert.Equal(1, state.BytesWritten); - - jsonUtf8 = new Utf8JsonWriter(output, state); - - Assert.Equal(1, jsonUtf8.CurrentDepth); - - jsonUtf8.WriteString("message", "Hello, World!"); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(); - - Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten); - - if (formatted) - Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesCommitted); - else - Assert.Equal(26, jsonUtf8.BytesCommitted); - - Assert.Equal(1, state.BytesCommitted); - Assert.Equal(1, state.BytesWritten); - - state = jsonUtf8.GetCurrentState(); - - if (formatted) - { - Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, state.BytesCommitted); - Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, state.BytesWritten); - } - else - { - Assert.Equal(26, state.BytesCommitted); - Assert.Equal(26, state.BytesWritten); - } - - output.Dispose(); + Assert.Equal(0, jsonUtf8.BytesPending); } [Theory] @@ -1126,88 +1560,30 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WriteInvalidPartialJson(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - jsonUtf8.Flush(isFinalBlock: false); - - state = jsonUtf8.GetCurrentState(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(1, jsonUtf8.BytesPending); - Assert.Equal(1, state.BytesCommitted); - Assert.Equal(1, state.BytesWritten); + jsonUtf8.Flush(); - jsonUtf8 = new Utf8JsonWriter(output, state); + Assert.Equal(1, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); - try + if (skipValidation) { jsonUtf8.WriteStringValue("Hello, World!"); - WriterDidNotThrow(skipValidation); - } - catch (InvalidOperationException) { } - try - { jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); - } - catch (InvalidOperationException) { } - - output.Dispose(); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritePartialJsonSkipFlush(bool formatted, bool skipValidation) - { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteString("message", "Hello, World!"); - - Assert.Equal(1, jsonUtf8.CurrentDepth); - - try - { - state = jsonUtf8.GetCurrentState(); - Assert.True(false, "Expected InvalidOperationException when trying to get current state without flushing first."); } - catch (InvalidOperationException) - { - - } - finally + else { - jsonUtf8.Flush(isFinalBlock: false); - state = jsonUtf8.GetCurrentState(); + Assert.Throws(() => jsonUtf8.WriteStringValue("Hello, World!")); + Assert.Throws(() => jsonUtf8.WriteEndArray()); } - - if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, state.BytesWritten); - else - Assert.Equal(26, state.BytesWritten); - - Assert.Equal(jsonUtf8.BytesWritten, jsonUtf8.BytesCommitted); - - jsonUtf8 = new Utf8JsonWriter(output, state); - Assert.Equal(1, jsonUtf8.CurrentDepth); - Assert.Equal(0, jsonUtf8.BytesWritten); - Assert.Equal(0, jsonUtf8.BytesCommitted); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(); - - output.Dispose(); } [Theory] @@ -1218,10 +1594,9 @@ namespace System.Text.Json.Tests public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) { { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); @@ -1229,43 +1604,33 @@ namespace System.Text.Json.Tests Assert.Equal(0, jsonUtf8.CurrentDepth); - state = jsonUtf8.GetCurrentState(); - - jsonUtf8 = new Utf8JsonWriter(output, state); - - try + if (skipValidation) { jsonUtf8.WriteStartObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - output.Dispose(); + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject()); + } } { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - Assert.Equal(0, jsonUtf8.CurrentDepth); - state = jsonUtf8.GetCurrentState(); - - jsonUtf8 = new Utf8JsonWriter(output, state); - - try + if (skipValidation) { jsonUtf8.WriteStartObject("name"); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - output.Dispose(); + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject("name")); + } } } @@ -1286,18 +1651,18 @@ namespace System.Text.Json.Tests { string expectedStr = GetCommentExpectedString(prettyPrint: formatted, comment); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); for (int j = 0; j < 10; j++) { - WriteCommentValue(ref jsonUtf8, i, comment); + WriteCommentValue(jsonUtf8, i, comment); } switch (i) @@ -1313,32 +1678,27 @@ namespace System.Text.Json.Tests break; } - WriteCommentValue(ref jsonUtf8, i, comment); + WriteCommentValue(jsonUtf8, i, comment); jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } - private static void WriteCommentValue(ref Utf8JsonWriter jsonUtf8, int i, string comment) + private static void WriteCommentValue(Utf8JsonWriter jsonUtf8, int i, string comment) { switch (i) { case 0: - jsonUtf8.WriteCommentValue(comment, escape: false); + jsonUtf8.WriteCommentValue(comment); break; case 1: - jsonUtf8.WriteCommentValue(comment.AsSpan(), escape: false); + jsonUtf8.WriteCommentValue(comment.AsSpan()); break; case 2: - jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment), escape: false); + jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)); break; } } @@ -1348,17 +1708,102 @@ namespace System.Text.Json.Tests [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] + public void WriteInvalidComment(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + string comment = "comment is */ invalid"; + + Assert.Throws(() => jsonUtf8.WriteCommentValue(comment)); + Assert.Throws(() => jsonUtf8.WriteCommentValue(comment.AsSpan())); + Assert.Throws(() => jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment))); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteCommentsInvalidTextAllowed(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + string comment = "comment is * / valid"; + jsonUtf8.WriteCommentValue(comment); + jsonUtf8.WriteCommentValue(comment.AsSpan()); + jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)); + + comment = "comment is /* valid"; + jsonUtf8.WriteCommentValue(comment); + jsonUtf8.WriteCommentValue(comment.AsSpan()); + jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)); + + comment = "comment is / * valid even with unpaired surrogate \udc00 this part no longer visible"; + jsonUtf8.WriteCommentValue(comment); + jsonUtf8.WriteCommentValue(comment.AsSpan()); + + jsonUtf8.Flush(); + + // Explicitly skipping flushing here + var invalidUtf8 = new byte[2] { 0xc3, 0x28 }; + jsonUtf8.WriteCommentValue(invalidUtf8); + + string expectedStr = GetCommentExpectedString(prettyPrint: formatted); + AssertContents(expectedStr, output); + } + + private static string GetCommentExpectedString(bool prettyPrint) + { + var ms = new MemoryStream(); + TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); + + var json = new JsonTextWriter(streamWriter) + { + Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + StringEscapeHandling = StringEscapeHandling.EscapeHtml, + }; + + string comment = "comment is * / valid"; + json.WriteComment(comment); + json.WriteComment(comment); + json.WriteComment(comment); + + comment = "comment is /* valid"; + json.WriteComment(comment); + json.WriteComment(comment); + json.WriteComment(comment); + + comment = "comment is / * valid even with unpaired surrogate "; + json.WriteComment(comment); + json.WriteComment(comment); + + json.Flush(); + + return Encoding.UTF8.GetString(ms.ToArray()); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] public void WriteStrings(bool formatted, bool skipValidation) { string value = "temp"; string expectedStr = GetStringsExpectedString(prettyPrint: formatted, value); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); @@ -1375,27 +1820,13 @@ namespace System.Text.Json.Tests case 2: jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value)); break; - case 3: - jsonUtf8.WriteStringValue(value, escape: false); - break; - case 4: - jsonUtf8.WriteStringValue(value.AsSpan(), escape: false); - break; - case 5: - jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value), escape: false); - break; } } jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1423,87 +1854,51 @@ namespace System.Text.Json.Tests public void WriteHelloWorldEscaped(bool formatted, bool skipValidation, string key, string value) { string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml); - string expectedStrNoEscape = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 18; i++) + for (int i = 0; i < 9; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(key, value, escape: true); + jsonUtf8.WriteString(key, value); break; case 1: - jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: true); + jsonUtf8.WriteString(key.AsSpan(), value.AsSpan()); break; case 2: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: true); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value)); break; case 3: - jsonUtf8.WriteString(key, value.AsSpan(), escape: true); + jsonUtf8.WriteString(key, value.AsSpan()); break; case 4: - jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: true); + jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value)); break; case 5: - jsonUtf8.WriteString(key.AsSpan(), value, escape: true); + jsonUtf8.WriteString(key.AsSpan(), value); break; case 6: - jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: true); + jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value)); break; case 7: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: true); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value); break; case 8: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan(), escape: true); - break; - case 9: - jsonUtf8.WriteString(key, value, escape: false); - break; - case 10: - jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: false); - break; - case 11: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: false); - break; - case 12: - jsonUtf8.WriteString(key, value.AsSpan(), escape: false); - break; - case 13: - jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: false); - break; - case 14: - jsonUtf8.WriteString(key.AsSpan(), value, escape: false); - break; - case 15: - jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: false); - break; - case 16: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: false); - break; - case 17: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan(), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan()); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i >= 9) - Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - else - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1534,41 +1929,28 @@ namespace System.Text.Json.Tests string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - for (int i = 0; i < 4; i++) + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + for (int i = 0; i < 2; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(propertyName, value, escape: true); + jsonUtf8.WriteString(propertyName, value); break; case 1: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true); - break; - case 2: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml, escape: false); - jsonUtf8.WriteString(propertyName, value, escape: false); - break; - case 3: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml, escape: false); - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value)); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1595,41 +1977,28 @@ namespace System.Text.Json.Tests string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - for (int i = 0; i < 4; i++) + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + for (int i = 0; i < 2; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(propertyName, value, escape: true); + jsonUtf8.WriteString(propertyName, value); break; case 1: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true); - break; - case 2: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(propertyName, value, escape: false); - break; - case 3: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value)); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1647,41 +2016,28 @@ namespace System.Text.Json.Tests string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - for (int i = 0; i < 4; i++) + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + for (int i = 0; i < 2; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(propertyName, value, escape: true); + jsonUtf8.WriteString(propertyName, value); break; case 1: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true); - break; - case 2: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(propertyName, value, escape: false); - break; - case 3: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value)); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1692,58 +2048,73 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void InvalidUTF8(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + var validUtf8 = new byte[2] { 0xc3, 0xb1 }; // 0xF1 + var invalidUtf8 = new byte[2] { 0xc3, 0x28 }; jsonUtf8.WriteStartObject(); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 4; i++) { - try + switch (i) { - switch (i) - { - case 0: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0x28 }, escape: false); - AssertWriterThrow(noThrow: false); - break; - case 1: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0xb1 }, escape: false); - AssertWriterThrow(noThrow: true); - break; - case 2: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0x28 }, escape: false); - AssertWriterThrow(noThrow: false); - break; - case 3: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0xb1 }, escape: false); - AssertWriterThrow(noThrow: true); - break; - case 4: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0x28 }, escape: true); - AssertWriterThrow(noThrow: false); - break; - case 5: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0xb1 }, escape: true); - AssertWriterThrow(noThrow: false); - break; - case 6: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0x28 }, escape: true); - AssertWriterThrow(noThrow: false); - break; - case 7: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0xb1 }, escape: true); - AssertWriterThrow(noThrow: true); - break; - } + case 0: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf8, invalidUtf8)); + break; + case 1: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf8, validUtf8)); + break; + case 2: + Assert.Throws(() => jsonUtf8.WriteString(validUtf8, invalidUtf8)); + break; + case 3: + jsonUtf8.WriteString(validUtf8, validUtf8); + break; } - catch (ArgumentException) { } } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); + } - output.Dispose(); + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidUTF16(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + var validUtf16 = new char[2] { (char)0xD801, (char)0xDC37 }; // 0x10437 + var invalidUtf16 = new char[2] { (char)0xD801, 'a' }; + + jsonUtf8.WriteStartObject(); + for (int i = 0; i < 4; i++) + { + switch (i) + { + case 0: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf16, invalidUtf16)); + break; + case 1: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf16, validUtf16)); + break; + case 2: + Assert.Throws(() => jsonUtf8.WriteString(validUtf16, invalidUtf16)); + break; + case 3: + jsonUtf8.WriteString(validUtf16, validUtf16); + break; + } + } + jsonUtf8.WriteEndObject(); + jsonUtf8.Flush(); } [Theory] @@ -1753,25 +2124,21 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WriteCustomStrings(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); for (int i = 0; i < 1_000; i++) - jsonUtf8.WriteString("message", "Hello, World!", escape: false); + { + jsonUtf8.WriteString("message", "Hello, World!"); + } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(GetCustomExpectedString(formatted), actualStr); - - output.Dispose(); + AssertContents(GetCustomExpectedString(formatted), output); } [Theory] @@ -1783,11 +2150,10 @@ namespace System.Text.Json.Tests { string expectedStr = GetStartEndExpectedString(prettyPrint: formatted); - var output = new ArrayBufferWriter(1024); - - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); jsonUtf8.WriteStartObject(); @@ -1795,12 +2161,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } [Theory] @@ -1811,43 +2172,31 @@ namespace System.Text.Json.Tests { string expectedStr = "[}"; - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true }; + var output = new ArrayBufferWriter(1024); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true }); - - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } { string expectedStr = "{]"; - var output = new ArrayBufferWriter(1024); - - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1860,34 +2209,25 @@ namespace System.Text.Json.Tests { string expectedStr = GetStartEndWithPropertyArrayExpectedString(prettyPrint: formatted); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartArray("property name", escape: false); + jsonUtf8.WriteStartArray("property name"); break; case 1: - jsonUtf8.WriteStartArray("property name".AsSpan(), escape: false); + jsonUtf8.WriteStartArray("property name".AsSpan()); break; case 2: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"), escape: false); - break; - case 3: - jsonUtf8.WriteStartArray("property name", escape: true); - break; - case 4: - jsonUtf8.WriteStartArray("property name".AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"), escape: true); + jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name")); break; } @@ -1895,12 +2235,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1923,36 +2258,26 @@ namespace System.Text.Json.Tests var key = new string(keyChars); string expectedStr = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: true); - string expectedStrNoEscape = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartArray(key, escape: false); + jsonUtf8.WriteStartArray(key); break; case 1: - jsonUtf8.WriteStartArray(key.AsSpan(), escape: false); + jsonUtf8.WriteStartArray(key.AsSpan()); break; case 2: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key), escape: false); - break; - case 3: - jsonUtf8.WriteStartArray(key, escape: true); - break; - case 4: - jsonUtf8.WriteStartArray(key.AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key), escape: true); + jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key)); break; } @@ -1960,15 +2285,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1981,34 +2298,25 @@ namespace System.Text.Json.Tests { string expectedStr = GetStartEndWithPropertyObjectExpectedString(prettyPrint: formatted); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartObject("property name", escape: false); + jsonUtf8.WriteStartObject("property name"); break; case 1: - jsonUtf8.WriteStartObject("property name".AsSpan(), escape: false); + jsonUtf8.WriteStartObject("property name".AsSpan()); break; case 2: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"), escape: false); - break; - case 3: - jsonUtf8.WriteStartObject("property name", escape: true); - break; - case 4: - jsonUtf8.WriteStartObject("property name".AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"), escape: true); + jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name")); break; } @@ -2016,12 +2324,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2044,52 +2347,34 @@ namespace System.Text.Json.Tests var key = new string(keyChars); string expectedStr = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: true); - string expectedStrNoEscape = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartObject(key, escape: false); + jsonUtf8.WriteStartObject(key); break; case 1: - jsonUtf8.WriteStartObject(key.AsSpan(), escape: false); + jsonUtf8.WriteStartObject(key.AsSpan()); break; case 2: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key), escape: false); - break; - case 3: - jsonUtf8.WriteStartObject(key, escape: true); - break; - case 4: - jsonUtf8.WriteStartObject(key.AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key), escape: true); + jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key)); break; } jsonUtf8.WriteEndObject(); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(); - - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); + jsonUtf8.WriteEndObject(); + jsonUtf8.Flush(); - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2102,25 +2387,25 @@ namespace System.Text.Json.Tests { string expectedStr = GetArrayWithPropertyExpectedString(prettyPrint: formatted); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartArray("message", escape: false); + jsonUtf8.WriteStartArray("message"); break; case 1: - jsonUtf8.WriteStartArray("message".AsSpan(), escape: false); + jsonUtf8.WriteStartArray("message".AsSpan()); break; case 2: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message"), escape: false); + jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message")); break; } @@ -2128,12 +2413,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2165,36 +2445,26 @@ namespace System.Text.Json.Tests public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, string keyString) { string expectedStr = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: true); - string expectedStrNoEscape = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteBoolean(keyString, value, escape: false); + jsonUtf8.WriteBoolean(keyString, value); break; case 1: - jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: false); + jsonUtf8.WriteBoolean(keyString.AsSpan(), value); break; case 2: - jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value, escape: false); - break; - case 3: - jsonUtf8.WriteBoolean(keyString, value, escape: true); - break; - case 4: - jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: true); - break; - case 5: - jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value, escape: true); + jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value); break; } @@ -2208,15 +2478,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStrNoEscape}, | Actual: {actualStr}, | Value: {value}"); - else - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}, | Value: {value}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2236,36 +2498,29 @@ namespace System.Text.Json.Tests public void WriteNullValue(bool formatted, bool skipValidation, string keyString) { string expectedStr = GetNullExpectedString(prettyPrint: formatted, keyString, escape: true); - string expectedStrNoEscape = GetNullExpectedString(prettyPrint: formatted, keyString, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(16); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteNull(keyString, escape: false); + jsonUtf8.WriteNull(keyString); + jsonUtf8.WriteNull(keyString); break; case 1: - jsonUtf8.WriteNull(keyString.AsSpan(), escape: false); + jsonUtf8.WriteNull(keyString.AsSpan()); + jsonUtf8.WriteNull(keyString.AsSpan()); break; case 2: - jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString), escape: false); - break; - case 3: - jsonUtf8.WriteNull(keyString, escape: true); - break; - case 4: - jsonUtf8.WriteNull(keyString.AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString), escape: true); + jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString)); + jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString)); break; } @@ -2277,15 +2532,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStrNoEscape}, | Actual: {actualStr}"); - else - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2318,37 +2565,32 @@ namespace System.Text.Json.Tests { string expectedStr = GetIntegerExpectedString(prettyPrint: formatted, value); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteNumber("message", value, escape: false); + jsonUtf8.WriteNumber("message", value); break; case 1: - jsonUtf8.WriteNumber("message".AsSpan(), value, escape: false); + jsonUtf8.WriteNumber("message".AsSpan(), value); break; case 2: - jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value, escape: false); + jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}, | Value: {value}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2493,14 +2735,13 @@ namespace System.Text.Json.Tests } string expectedStr = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false); - string expectedStrNoEscape = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int j = 0; j < 6; j++) + for (int j = 0; j < 3; j++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); @@ -2511,105 +2752,54 @@ namespace System.Text.Json.Tests { case 0: for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyString, floats[i], escape: false); + jsonUtf8.WriteNumber(keyString, floats[i]); for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyString, ints[i], escape: false); + jsonUtf8.WriteNumber(keyString, ints[i]); for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyString, uints[i], escape: false); + jsonUtf8.WriteNumber(keyString, uints[i]); for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyString, doubles[i], escape: false); + jsonUtf8.WriteNumber(keyString, doubles[i]); for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyString, longs[i], escape: false); + jsonUtf8.WriteNumber(keyString, longs[i]); for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyString, ulongs[i], escape: false); + jsonUtf8.WriteNumber(keyString, ulongs[i]); for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyString, decimals[i], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteNumber(keyString, decimals[i]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, floats[i]); for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, ints[i]); for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, uints[i]); for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, doubles[i]); for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, longs[i]); for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, ulongs[i]); for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteNumber(keyUtf16, decimals[i]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, floats[i], escape: false); - for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: false); - for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: false); - for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: false); - for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: false); - for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: false); - for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyString, floats[i], escape: true); - for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyString, ints[i], escape: true); - for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyString, uints[i], escape: true); - for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyString, doubles[i], escape: true); - for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyString, longs[i], escape: true); - for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyString, ulongs[i], escape: true); - for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyString, decimals[i], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: true); - for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: true); - for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: true); - for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: true); - for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: true); - for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: true); - for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, floats[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, floats[i]); for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, ints[i]); for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, uints[i]); for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, doubles[i]); for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, longs[i]); for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, ulongs[i]); for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteNumber(keyUtf8, decimals[i]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2625,17 +2815,205 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - // TODO: The output doesn't match what JSON.NET does (different rounding/e-notation). - //if (j < 3) - // Assert.Equal(expectedStrNoEscape, actualStr); - //else - // Assert.Equal(expectedStr, actualStr); + // AssertContents(expectedStr, output); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueInt32(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue(1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueInt64(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((long)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueUInt32(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((uint)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueUInt64(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((ulong)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueSingle(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); - output.Dispose(); + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((float)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueDouble(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((double)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueDecimal(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((decimal)1234567); + numberOfElements++; } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); } [Theory] @@ -2657,20 +3035,21 @@ namespace System.Text.Json.Tests var guids = new Guid[numberOfItems]; for (int i = 0; i < numberOfItems; i++) + { guids[i] = Guid.NewGuid(); + } string expectedStr = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: true); - string expectedStrNoEscape = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2678,33 +3057,18 @@ namespace System.Text.Json.Tests { case 0: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, guids[j], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteString(keyString, guids[j]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, guids[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteString(keyUtf16, guids[j]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, guids[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, guids[j], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, guids[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, guids[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteString(keyUtf8, guids[j]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2715,15 +3079,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2753,17 +3109,16 @@ namespace System.Text.Json.Tests dates[i] = start.AddDays(random.Next(range)); string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true); - string expectedStrNoEscape = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2771,33 +3126,18 @@ namespace System.Text.Json.Tests { case 0: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteString(keyString, dates[j]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteString(keyUtf16, dates[j]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteString(keyUtf8, dates[j]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2808,15 +3148,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2846,17 +3178,16 @@ namespace System.Text.Json.Tests dates[i] = new DateTimeOffset(start.AddDays(random.Next(range))); string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true); - string expectedStrNoEscape = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2864,33 +3195,18 @@ namespace System.Text.Json.Tests { case 0: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteString(keyString, dates[j]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteString(keyUtf16, dates[j]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteString(keyUtf8, dates[j]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2901,15 +3217,7 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2921,10 +3229,8 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - Span key; - Span value; + byte[] key; + byte[] value; try { @@ -2936,30 +3242,25 @@ namespace System.Text.Json.Tests return; } - key.Fill((byte)'a'); - value.Fill((byte)'b'); + key.AsSpan().Fill((byte)'a'); + value.AsSpan().Fill((byte)'b'); - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - try { - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); - jsonUtf8.WriteString(key, DateTime.Now, escape: false); - Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. KeyLength: {key.Length}"); + Assert.Throws(() => jsonUtf8.WriteString(key, DateTime.Now)); + Assert.Equal(0, output.WrittenCount); } - catch (ArgumentException) { } - try { - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStringValue(value, escape: false); - Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. ValueLength: {value.Length}"); + Assert.Throws(() => jsonUtf8.WriteStringValue(value)); + Assert.Equal(0, output.WrittenCount); } - catch (ArgumentException) { } - - output.Dispose(); } [ConditionalTheory(nameof(IsX64))] @@ -2970,7 +3271,7 @@ namespace System.Text.Json.Tests [InlineData(false, false)] public void WriteLargeKeyValue(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; Span key; Span value; @@ -2985,23 +3286,23 @@ namespace System.Text.Json.Tests return; } - WriteTooLargeHelper(state, key, value); - WriteTooLargeHelper(state, key.Slice(0, 1_000_000_000), value); - WriteTooLargeHelper(state, key, value.Slice(0, 1_000_000_000)); - WriteTooLargeHelper(state, key.Slice(0, 10_000_000 / 3), value.Slice(0, 10_000_000 / 3), noThrow: true); + WriteTooLargeHelper(options, key, value); + WriteTooLargeHelper(options, key.Slice(0, 1_000_000_000), value); + WriteTooLargeHelper(options, key, value.Slice(0, 1_000_000_000)); + WriteTooLargeHelper(options, key.Slice(0, 10_000_000 / 3), value.Slice(0, 10_000_000 / 3), noThrow: true); } - private static void WriteTooLargeHelper(JsonWriterState state, ReadOnlySpan key, ReadOnlySpan value, bool noThrow = false) + private static void WriteTooLargeHelper(JsonWriterOptions options, ReadOnlySpan key, ReadOnlySpan value, bool noThrow = false) { // Resizing is too slow, even for outerloop tests, so initialize to a large output size up front. - var output = new ArrayBufferWriter(noThrow ? 40_000_000 : 1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(noThrow ? 40_000_000 : 1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); try { - jsonUtf8.WriteString(key, value, escape: false); + jsonUtf8.WriteString(key, value); if (!noThrow) { @@ -3018,47 +3319,24 @@ namespace System.Text.Json.Tests jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - - output.Dispose(); - } - - private static void WriterDidNotThrow(bool skipValidation) - { - if (skipValidation) - Assert.True(true, "Did not expect InvalidOperationException to be thrown since validation was skipped."); - else - Assert.True(false, "Expected InvalidOperationException to be thrown when validation is enabled."); - } - - private static void WriterDidNotThrow(bool skipValidation, string message) - { - if (skipValidation) - Assert.True(true, message); - else - Assert.True(false, message); - } - - private static void AssertWriterThrow(bool noThrow) - { - if (noThrow) - Assert.True(true, "Did not expect InvalidOperationException to be thrown since input was valid (or suppressEscaping was true)."); - else - Assert.True(false, "Expected InvalidOperationException to be thrown when user passes invalid UTF-8."); } - private static string GetHelloWorldExpectedString(bool prettyPrint) + private static string GetHelloWorldExpectedString(bool prettyPrint, string propertyName, string value) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) { - Formatting = prettyPrint ? Formatting.Indented : Formatting.None + Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + StringEscapeHandling = StringEscapeHandling.EscapeHtml }; json.WriteStartObject(); - json.WritePropertyName("message"); - json.WriteValue("Hello, World!"); + json.WritePropertyName(propertyName); + json.WriteValue(value); + json.WritePropertyName(propertyName); + json.WriteValue(value); json.WriteEnd(); json.Flush(); @@ -3068,7 +3346,7 @@ namespace System.Text.Json.Tests private static string GetCommentExpectedString(bool prettyPrint, string comment) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3091,7 +3369,7 @@ namespace System.Text.Json.Tests private static string GetStringsExpectedString(bool prettyPrint, string value) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3111,7 +3389,7 @@ namespace System.Text.Json.Tests private static string GetEscapedExpectedString(bool prettyPrint, string propertyName, string value, StringEscapeHandling escaping, bool escape = true) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3132,7 +3410,7 @@ namespace System.Text.Json.Tests private static string GetCustomExpectedString(bool prettyPrint) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3155,7 +3433,7 @@ namespace System.Text.Json.Tests private static string GetStartEndExpectedString(bool prettyPrint) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3175,7 +3453,7 @@ namespace System.Text.Json.Tests private static string GetStartEndWithPropertyArrayExpectedString(bool prettyPrint) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3196,7 +3474,7 @@ namespace System.Text.Json.Tests private static string GetStartEndWithPropertyArrayExpectedString(string key, bool prettyPrint, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3218,7 +3496,7 @@ namespace System.Text.Json.Tests private static string GetStartEndWithPropertyObjectExpectedString(bool prettyPrint) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3239,7 +3517,7 @@ namespace System.Text.Json.Tests private static string GetStartEndWithPropertyObjectExpectedString(string key, bool prettyPrint, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3261,7 +3539,7 @@ namespace System.Text.Json.Tests private static string GetArrayWithPropertyExpectedString(bool prettyPrint) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3281,7 +3559,7 @@ namespace System.Text.Json.Tests private static string GetBooleanExpectedString(bool prettyPrint, string keyString, bool value, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3311,7 +3589,7 @@ namespace System.Text.Json.Tests private static string GetNullExpectedString(bool prettyPrint, string keyString, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3323,6 +3601,8 @@ namespace System.Text.Json.Tests json.WriteStartObject(); json.WritePropertyName(keyString, escape); json.WriteNull(); + json.WritePropertyName(keyString, escape); + json.WriteNull(); json.WritePropertyName("temp"); json.WriteStartArray(); @@ -3339,7 +3619,7 @@ namespace System.Text.Json.Tests private static string GetIntegerExpectedString(bool prettyPrint, int value) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3359,7 +3639,7 @@ namespace System.Text.Json.Tests private static string GetNumbersExpectedString(bool prettyPrint, string keyString, int[] ints, uint[] uints, long[] longs, ulong[] ulongs, float[] floats, double[] doubles, decimal[] decimals, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3425,7 +3705,7 @@ namespace System.Text.Json.Tests private static string GetGuidsExpectedString(bool prettyPrint, string keyString, Guid[] guids, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3455,9 +3735,31 @@ namespace System.Text.Json.Tests return Encoding.UTF8.GetString(ms.ToArray()); } + private static string GetNumbersExpectedString(bool prettyPrint, int numberOfElements) + { + var ms = new MemoryStream(); + TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); + + var json = new JsonTextWriter(streamWriter) + { + Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + }; + + json.WriteStartArray(); + for (int i = 0; i < numberOfElements; i++) + { + json.WriteValue(1234567); + } + json.WriteEnd(); + + json.Flush(); + + return Encoding.UTF8.GetString(ms.ToArray()); + } + private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTime[] dates, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3489,7 +3791,7 @@ namespace System.Text.Json.Tests private static string GetDatesExpectedString(bool prettyPrint, string keyString, DateTimeOffset[] dates, bool escape = false) { - MemoryStream ms = new MemoryStream(); + var ms = new MemoryStream(); TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); var json = new JsonTextWriter(streamWriter) @@ -3518,5 +3820,17 @@ namespace System.Text.Json.Tests return Encoding.UTF8.GetString(ms.ToArray()); } + + private static void AssertContents(string expectedValue, ArrayBufferWriter buffer) + { + Assert.Equal( + expectedValue, + Encoding.UTF8.GetString( + buffer.WrittenSpan +#if netstandard + .ToArray() +#endif + )); + } } } -- 2.7.4