From ad30f5439b0e286c20136b8e7aece32b3b55f22e Mon Sep 17 00:00:00 2001 From: Gan Keyu Date: Sat, 5 Aug 2023 23:15:10 +0800 Subject: [PATCH] Avoid string allocation in WriteTo when possible. System.Text.Json.JsonProperty.WriteTo uses get_Name, calling JsonElement.GetPropertyName() which would allocate a string. Use ReadOnlySpan from the underlying UTF8 json, when possible, by adding helper methods into JsonDocument & JsonElement. Fix #88767 --- .../System/Text/Json/Document/JsonDocument.cs | 29 +++++++++++++++++++ .../System/Text/Json/Document/JsonElement.cs | 7 +++++ .../System/Text/Json/Document/JsonProperty.cs | 10 ++++++- 3 files changed, 45 insertions(+), 1 deletion(-) 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 93f7143a55844..0620579615767 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 @@ -283,6 +283,29 @@ private ReadOnlyMemory GetPropertyRawValue(int valueIndex) : JsonReaderHelper.TranscodeHelper(segment); } + internal ReadOnlySpan GetUtf8Span(int index, JsonTokenType expectedType) + { + CheckNotDisposed(); + + DbRow row = _parsedData.Get(index); + + JsonTokenType tokenType = row.TokenType; + + if (tokenType == JsonTokenType.Null) + { + return default; + } + + CheckExpectedType(expectedType, tokenType); + + ReadOnlySpan data = _utf8Json.Span; + ReadOnlySpan segment = data.Slice(row.Location, row.SizeOrLength); + + return row.HasComplexChildren + ? JsonReaderHelper.GetUnescapedSpan(segment) + : segment; + } + internal bool TextEquals(int index, ReadOnlySpan otherText, bool isPropertyName) { CheckNotDisposed(); @@ -363,6 +386,12 @@ internal string GetNameOfPropertyValue(int index) return GetString(index - DbRow.Size, JsonTokenType.PropertyName)!; } + internal ReadOnlySpan GetNameOfPropertyValueAsUtf8Span(int index) + { + // The property name is one row before the property value + return GetUtf8Span(index - DbRow.Size, JsonTokenType.PropertyName)!; + } + internal bool TryGetValue(int index, [NotNullWhen(true)] out byte[]? value) { CheckNotDisposed(); 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 77732b69548d6..f7dad1edb17b1 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 @@ -1163,6 +1163,13 @@ internal string GetPropertyName() return _parent.GetNameOfPropertyValue(_idx); } + internal ReadOnlySpan GetPropertyNameAsUtf8Span() + { + CheckValidInstance(); + + return _parent.GetNameOfPropertyValueAsUtf8Span(_idx); + } + /// /// Gets the original input data backing this value, returning it as a . /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs index 61a66b689ebcb..71945d656227c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs @@ -117,7 +117,15 @@ public void WriteTo(Utf8JsonWriter writer) ThrowHelper.ThrowArgumentNullException(nameof(writer)); } - writer.WritePropertyName(Name); + if (_name is null) + { + writer.WritePropertyName(Value.GetPropertyNameAsUtf8Span()); + } + else + { + writer.WritePropertyName(_name); + } + Value.WriteTo(writer); }