Skip to content

Commit 2b9a5ec

Browse files
committed
Add write-raw APIs to Utf8JsonWriter
1 parent aa558fa commit 2b9a5ec

File tree

8 files changed

+383
-8
lines changed

8 files changed

+383
-8
lines changed

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,9 @@ public void WritePropertyName(System.ReadOnlySpan<byte> utf8PropertyName) { }
461461
public void WritePropertyName(System.ReadOnlySpan<char> propertyName) { }
462462
public void WritePropertyName(string propertyName) { }
463463
public void WritePropertyName(System.Text.Json.JsonEncodedText propertyName) { }
464+
public void WriteRawValue(string json, bool skipInputValidation = false) { }
465+
public void WriteRawValue(System.ReadOnlySpan<byte> utf8Json, bool skipInputValidation = false) { }
466+
public void WriteRawValue(System.ReadOnlySpan<char> json, bool skipInputValidation = false) { }
464467
public void WriteStartArray() { }
465468
public void WriteStartArray(System.ReadOnlySpan<byte> utf8PropertyName) { }
466469
public void WriteStartArray(System.ReadOnlySpan<char> propertyName) { }

src/libraries/System.Text.Json/src/System.Text.Json.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@
257257
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Guid.cs" />
258258
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Helpers.cs" />
259259
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Literal.cs" />
260+
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.Raw.cs" />
260261
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.SignedNumber.cs" />
261262
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.String.cs" />
262263
<Compile Include="System\Text\Json\Writer\Utf8JsonWriter.WriteValues.UnsignedNumber.cs" />

src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ internal static class JsonConstants
6464
// All other UTF-16 characters can be represented by either 1 or 2 UTF-8 bytes.
6565
public const int MaxExpansionFactorWhileTranscoding = 3;
6666

67+
// When transcoding from UTF8 -> UTF16, the byte count threshold where we rent from the array pool before performing a normal alloc.
68+
public const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024;
69+
6770
public const int MaxEscapedTokenSize = 1_000_000_000; // Max size for already escaped value.
6871
public const int MaxUnescapedTokenSize = MaxEscapedTokenSize / MaxExpansionFactorWhileEscaping; // 166_666_666 bytes
6972
public const int MaxBase64ValueTokenSize = (MaxEscapedTokenSize >> 2) * 3 / MaxExpansionFactorWhileEscaping; // 125_000_000 bytes

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,10 @@ public static partial class JsonSerializer
355355

356356
private static TValue? ReadUsingMetadata<TValue>(ReadOnlySpan<char> json, JsonTypeInfo jsonTypeInfo)
357357
{
358-
const long ArrayPoolMaxSizeBeforeUsingNormalAlloc = 1024 * 1024;
359-
360358
byte[]? tempArray = null;
361359

362360
// For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold.
363-
Span<byte> utf8 = json.Length <= (ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ?
361+
Span<byte> utf8 = json.Length <= (JsonConstants.ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ?
364362
// Use a pooled alloc.
365363
tempArray = ArrayPool<byte>.Shared.Rent(json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) :
366364
// Use a normal alloc since the pool would create a normal alloc anyway based on the threshold (per current implementation)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Buffers;
5+
6+
namespace System.Text.Json
7+
{
8+
public sealed partial class Utf8JsonWriter
9+
{
10+
/// <summary>
11+
/// Writes the input as JSON content.
12+
/// </summary>
13+
/// <param name="json">The raw JSON content to write.</param>
14+
/// <param name="skipInputValidation">Whether to skip validation of the input JSON content.</param>
15+
public void WriteRawValue(string json, bool skipInputValidation = false)
16+
{
17+
if (json == null)
18+
{
19+
throw new ArgumentNullException(nameof(json));
20+
}
21+
22+
WriteRawValue(json.AsSpan(), skipInputValidation);
23+
}
24+
25+
/// <summary>
26+
/// Writes the input as JSON content.
27+
/// </summary>
28+
/// <param name="json">The raw JSON content to write.</param>
29+
/// <param name="skipInputValidation">Whether to skip validation of the input JSON content.</param>
30+
public void WriteRawValue(ReadOnlySpan<char> json, bool skipInputValidation = false)
31+
{
32+
byte[]? tempArray = null;
33+
34+
// For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold.
35+
Span<byte> utf8Json = json.Length <= (JsonConstants.ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ?
36+
// Use a pooled alloc.
37+
tempArray = ArrayPool<byte>.Shared.Rent(json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) :
38+
// Use a normal alloc since the pool would create a normal alloc anyway based on the threshold (per current implementation)
39+
// and by using a normal alloc we can avoid the Clear().
40+
new byte[JsonReaderHelper.GetUtf8ByteCount(json)];
41+
42+
try
43+
{
44+
int actualByteCount = JsonReaderHelper.GetUtf8FromText(json, utf8Json);
45+
utf8Json = utf8Json.Slice(0, actualByteCount);
46+
WriteRawValue(utf8Json, skipInputValidation);
47+
}
48+
finally
49+
{
50+
if (tempArray != null)
51+
{
52+
utf8Json.Clear();
53+
ArrayPool<byte>.Shared.Return(tempArray);
54+
}
55+
}
56+
}
57+
58+
/// <summary>
59+
/// Writes the input as JSON content.
60+
/// </summary>
61+
/// <param name="utf8Json">The raw JSON content to write.</param>
62+
/// <param name="skipInputValidation">Whether to skip validation of the input JSON content.</param>
63+
public void WriteRawValue(ReadOnlySpan<byte> utf8Json, bool skipInputValidation = false)
64+
{
65+
if (utf8Json.Length == 0)
66+
{
67+
ThrowHelper.ThrowArgumentException(SR.ExpectedJsonTokens);
68+
}
69+
70+
if (!skipInputValidation)
71+
{
72+
Utf8JsonReader reader = new Utf8JsonReader(utf8Json);
73+
74+
try
75+
{
76+
while (reader.Read());
77+
}
78+
catch (JsonReaderException ex)
79+
{
80+
ThrowHelper.ThrowArgumentException(ex.Message);
81+
}
82+
}
83+
84+
int maxRequired = utf8Json.Length + 1; // Optionally, 1 list separator
85+
86+
if (_memory.Length - BytesPending < maxRequired)
87+
{
88+
Grow(maxRequired);
89+
}
90+
91+
Span<byte> output = _memory.Span;
92+
93+
if (_currentDepth < 0)
94+
{
95+
output[BytesPending++] = JsonConstants.ListSeparator;
96+
}
97+
98+
utf8Json.CopyTo(output.Slice(BytesPending));
99+
BytesPending += utf8Json.Length;
100+
101+
SetFlagToAddListSeparatorBeforeNextItem();
102+
103+
// Treat all raw JSON value writes as string.
104+
_tokenType = JsonTokenType.String;
105+
}
106+
}
107+
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
</ItemGroup>
190190
<ItemGroup>
191191
<Compile Include="..\..\src\System\Text\Json\BitStack.cs" Link="BitStack.cs" />
192+
<Compile Include="Utf8JsonWriterTests.WriteRaw.cs" />
192193
</ItemGroup>
193194
<ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
194195
<Compile Include="$(CommonPath)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />

0 commit comments

Comments
 (0)