Skip to content

Commit 230538b

Browse files
authored
Improve deserialization perf for case-insensitive and missing-property cases (#35848)
1 parent 2d4acbd commit 230538b

14 files changed

Lines changed: 324 additions & 185 deletions

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ private JsonEncodedText(byte[] utf8Value)
3232
_utf8Value = utf8Value;
3333
}
3434

35+
private JsonEncodedText(string stringValue, byte[] utf8Value)
36+
{
37+
Debug.Assert(stringValue != null);
38+
Debug.Assert(utf8Value != null);
39+
40+
_value = stringValue;
41+
_utf8Value = utf8Value;
42+
}
43+
3544
/// <summary>
3645
/// Encodes the string text value as a JSON string.
3746
/// </summary>
@@ -125,6 +134,37 @@ private static JsonEncodedText EncodeHelper(ReadOnlySpan<byte> utf8Value, JavaSc
125134
}
126135
}
127136

137+
/// <summary>
138+
/// Internal version that keeps the existing string and byte[] references if there is no escaping required.
139+
/// </summary>
140+
internal static JsonEncodedText Encode(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder = null)
141+
{
142+
Debug.Assert(stringValue.Equals(JsonHelpers.Utf8GetString(utf8Value)));
143+
144+
if (utf8Value.Length == 0)
145+
{
146+
return new JsonEncodedText(stringValue, utf8Value);
147+
}
148+
149+
JsonWriterHelper.ValidateValue(utf8Value);
150+
return EncodeHelper(stringValue, utf8Value, encoder);
151+
}
152+
153+
private static JsonEncodedText EncodeHelper(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder)
154+
{
155+
int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);
156+
157+
if (idx != -1)
158+
{
159+
return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
160+
}
161+
else
162+
{
163+
// Encoding is not necessary; use the same stringValue and utf8Value references.
164+
return new JsonEncodedText(stringValue, utf8Value);
165+
}
166+
}
167+
128168
private static byte[] GetEscapedString(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal, JavaScriptEncoder? encoder)
129169
{
130170
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.Large.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ internal sealed class LargeObjectWithParameterizedConstructorConverter<T> : Obje
1414
{
1515
protected override bool ReadAndCacheConstructorArgument(ref ReadStack state, ref Utf8JsonReader reader, JsonParameterInfo jsonParameterInfo)
1616
{
17-
bool success = jsonParameterInfo.ReadJson(ref state, ref reader, out object? arg0);
17+
bool success = jsonParameterInfo.ReadJson(ref state, ref reader, out object? arg);
1818

1919
if (success)
2020
{
21-
((object[])state.Current.CtorArgumentState!.Arguments)[jsonParameterInfo.Position] = arg0!;
21+
((object[])state.Current.CtorArgumentState!.Arguments)[jsonParameterInfo.Position] = arg!;
2222
}
2323

2424
return success;

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -454,41 +454,19 @@ private bool TryLookupConstructorParameter(
454454

455455
ReadOnlySpan<byte> unescapedPropertyName = JsonSerializer.GetPropertyName(ref state, ref reader, options);
456456

457-
if (!state.Current.JsonClassInfo.TryGetParameter(unescapedPropertyName, ref state.Current, out jsonParameterInfo))
458-
{
459-
return false;
460-
}
461-
462-
Debug.Assert(jsonParameterInfo != null);
457+
jsonParameterInfo = state.Current.JsonClassInfo.GetParameter(
458+
unescapedPropertyName,
459+
ref state.Current,
460+
out byte[] utf8PropertyName);
463461

464-
// Increment ConstructorParameterIndex so GetProperty() starts with the next parameter the next time this function is called.
462+
// Increment ConstructorParameterIndex so GetParameter() checks the next parameter first when called again.
465463
state.Current.CtorArgumentState!.ParameterIndex++;
466464

467-
// Support JsonException.Path.
468-
Debug.Assert(
469-
jsonParameterInfo.JsonPropertyName == null ||
470-
options.PropertyNameCaseInsensitive ||
471-
unescapedPropertyName.SequenceEqual(jsonParameterInfo.JsonPropertyName));
472-
473-
if (jsonParameterInfo.JsonPropertyName == null)
474-
{
475-
byte[] propertyNameArray = unescapedPropertyName.ToArray();
476-
if (options.PropertyNameCaseInsensitive)
477-
{
478-
// Each payload can have a different name here; remember the value on the temporary stack.
479-
state.Current.JsonPropertyName = propertyNameArray;
480-
}
481-
else
482-
{
483-
//Prevent future allocs by caching globally on the JsonPropertyInfo which is specific to a Type+PropertyName
484-
// so it will match the incoming payload except when case insensitivity is enabled(which is handled above).
485-
jsonParameterInfo.JsonPropertyName = propertyNameArray;
486-
}
487-
}
465+
// For case insensitive and missing property support of JsonPath, remember the value on the temporary stack.
466+
state.Current.JsonPropertyName = utf8PropertyName;
488467

489468
state.Current.CtorArgumentState.JsonParameterInfo = jsonParameterInfo;
490-
491-
return true;
469+
return jsonParameterInfo != null;
492470
}
493471

494472
internal override bool ConstructorIsParameterized => true;

0 commit comments

Comments
 (0)