Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,27 @@ private ReadOnlyMemory<byte> GetPropertyRawValue(int valueIndex)
: JsonReaderHelper.TranscodeHelper(segment);
}

internal ReadOnlySpan<byte> GetRawUtf8Span(int index, JsonTokenType expectedType)
{
CheckNotDisposed();

DbRow row = _parsedData.Get(index);

JsonTokenType tokenType = row.TokenType;

if (tokenType == JsonTokenType.Null)
{
return default;
}

CheckExpectedType(expectedType, tokenType);

ReadOnlySpan<byte> data = _utf8Json.Span;
ReadOnlySpan<byte> segment = data.Slice(row.Location, row.SizeOrLength);

return segment;
}

internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropertyName)
{
CheckNotDisposed();
Expand Down Expand Up @@ -363,6 +384,12 @@ internal string GetNameOfPropertyValue(int index)
return GetString(index - DbRow.Size, JsonTokenType.PropertyName)!;
}

internal ReadOnlySpan<byte> GetRawNameOfPropertyValueAsUtf8Span(int index)
{
// The property name is one row before the property value
return GetRawUtf8Span(index - DbRow.Size, JsonTokenType.PropertyName)!;
}

internal bool TryGetValue(int index, [NotNullWhen(true)] out byte[]? value)
{
CheckNotDisposed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,13 @@ internal string GetPropertyName()
return _parent.GetNameOfPropertyValue(_idx);
}

internal ReadOnlySpan<byte> GetRawPropertyNameAsUtf8Span()
{
CheckValidInstance();

return _parent.GetRawNameOfPropertyValueAsUtf8Span(_idx);
}

/// <summary>
/// Gets the original input data backing this value, returning it as a <see cref="string"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers;
using System.Diagnostics;

namespace System.Text.Json
Expand Down Expand Up @@ -117,7 +118,46 @@ public void WriteTo(Utf8JsonWriter writer)
ThrowHelper.ThrowArgumentNullException(nameof(writer));
}

writer.WritePropertyName(Name);
if (_name is null)
{
ReadOnlySpan<byte> rawName = Value.GetRawPropertyNameAsUtf8Span();
int firstBackSlashIndex = rawName.IndexOf(JsonConstants.BackSlash);
if (firstBackSlashIndex >= 0)
{
// Code is adapted from JsonReaderHelper.GetUnescapedSpan to avoid allocations further.
// writer.WritePropertyName(JsonReaderHelper.GetUnescapedSpan(rawName));

int length = rawName.Length;
byte[]? pooledName = null;

Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(pooledName = ArrayPool<byte>.Shared.Rent(length));

JsonReaderHelper.Unescape(rawName, utf8Unescaped, firstBackSlashIndex, out int written);
Debug.Assert(written > 0);

ReadOnlySpan<byte> propertyName = utf8Unescaped.Slice(0, written);
Debug.Assert(!propertyName.IsEmpty);

writer.WritePropertyName(propertyName);

if (pooledName != null)
{
new Span<byte>(pooledName, 0, written).Clear();
ArrayPool<byte>.Shared.Return(pooledName);
}
}
else
{
writer.WritePropertyName(rawName);
}
}
else
{
writer.WritePropertyName(_name);
}

Value.WriteTo(writer);
}

Expand Down