Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ internal sealed class ContextGenerationSpec
public HashSet<TypeGenerationSpec> TypesWithMetadataGenerated { get; } = new();

/// <summary>
/// Cache of runtime property names (statically determined) found accross the object graph of the JsonSerializerContext.
/// Cache of runtime property names (statically determined) found across the object graph of the JsonSerializerContext.
/// The dictionary Key is the JSON property name, and the Value is the variable name which is the same as the property
/// name except for cases where special characters are used with [JsonPropertyName].
/// </summary>
public HashSet<string> RuntimePropertyNames { get; } = new();
public Dictionary<string, string> RuntimePropertyNames { get; } = new();

public string ContextTypeRef => ContextType.GetCompilableName();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Text.Json.Reflection;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;

namespace System.Text.Json.SourceGeneration
Expand Down Expand Up @@ -803,15 +804,16 @@ private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec)
TypeGenerationSpec propertyTypeSpec = propertyGenSpec.TypeGenerationSpec;

string runtimePropName = propertyGenSpec.RuntimePropertyName;
string propVarName = propertyGenSpec.PropertyNameVarName;

// Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation.
_currentContext.RuntimePropertyNames.Add(runtimePropName);
Debug.Assert(!_currentContext.RuntimePropertyNames.TryGetValue(runtimePropName, out string? existingName) || existingName == propVarName);
_currentContext.RuntimePropertyNames.TryAdd(runtimePropName, propVarName);

Type propertyType = propertyTypeSpec.Type;
string propName = $"{runtimePropName}PropName";
string? objectRef = castingRequiredForProps ? $"(({propertyGenSpec.DeclaringTypeRef}){ValueVarName})" : ValueVarName;
string propValue = $"{objectRef}.{propertyGenSpec.ClrName}";
string methodArgs = $"{propName}, {propValue}";
string methodArgs = $"{propVarName}, {propValue}";

string? methodToCall = GetWriterMethod(propertyType);

Expand All @@ -830,7 +832,7 @@ private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec)
else
{
serializationLogic = $@"
{WriterVarName}.WritePropertyName({propName});
{WriterVarName}.WritePropertyName({propVarName});
{GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}";
}

Expand Down Expand Up @@ -1190,10 +1192,10 @@ private string GetPropertyNameInitialization()

StringBuilder sb = new();

foreach (string propName in _currentContext.RuntimePropertyNames)
foreach (KeyValuePair<string, string> name_varName_pair in _currentContext.RuntimePropertyNames)
{
sb.Append($@"
private static {JsonEncodedTextTypeRef} {propName}PropName = {JsonEncodedTextTypeRef}.Encode(""{propName}"");");
private static readonly {JsonEncodedTextTypeRef} {name_varName_pair.Value} = {JsonEncodedTextTypeRef}.Encode(""{name_varName_pair.Key}"");");
}

return sb.ToString();
Expand Down
36 changes: 35 additions & 1 deletion src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,8 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
}

string clrName = memberInfo.Name;
string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy);
string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName);

return new PropertyGenerationSpec
{
Expand All @@ -1022,7 +1024,8 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
IsPublic = isPublic,
IsVirtual = isVirtual,
JsonPropertyName = jsonPropertyName,
RuntimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy),
RuntimePropertyName = runtimePropertyName,
PropertyNameVarName = propertyNameVarName,
IsReadOnly = isReadOnly,
CanUseGetter = canUseGetter,
CanUseSetter = canUseSetter,
Expand Down Expand Up @@ -1079,6 +1082,37 @@ private static string DetermineRuntimePropName(string clrPropName, string? jsonP
return runtimePropName;
}

private static string DeterminePropNameIdentifier(string runtimePropName)
{
const string PropName = "PropName_";

// Use a different prefix to avoid possible collisions with "PropName_" in
// the rare case there is a C# property in a hex format.
const string EncodedPropName = "EncodedPropName_";

if (SyntaxFacts.IsValidIdentifier(runtimePropName))
{
return PropName + runtimePropName;
}

// Encode the string to a byte[] and then convert to hexadecimal.
// To make the generated code more readable, we could use a different strategy in the future
// such as including the full class name + the CLR property name when there are duplicates,
// but that will create unnecessary JsonEncodedText properties.
byte[] utf8Json = Encoding.UTF8.GetBytes(runtimePropName);

StringBuilder sb = new StringBuilder(
EncodedPropName,
capacity: EncodedPropName.Length + utf8Json.Length * 2);

for (int i = 0; i < utf8Json.Length; i++)
{
sb.Append(utf8Json[i].ToString("X2")); // X2 is hex format
}

return sb.ToString();
}

private void PopulateNumberTypes()
{
Debug.Assert(_numberTypes != null);
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ internal sealed class PropertyGenerationSpec
/// </summary>
public string RuntimePropertyName { get; init; }

public string PropertyNameVarName { get; init; }

/// <summary>
/// Whether the property has a set method.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ internal static void Unescape(ReadOnlySpan<byte> source, Span<byte> destination,
else if (currentByte == 'u')
{
// The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
// Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
// Otherwise, the Utf8JsonReader would have already thrown an exception.
Debug.Assert(source.Length >= idx + 5);

bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x');
Expand All @@ -399,7 +399,7 @@ internal static void Unescape(ReadOnlySpan<byte> source, Span<byte> destination,
}

// The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
// Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
// Otherwise, the Utf8JsonReader would have already thrown an exception.
result = Utf8Parser.TryParse(source.Slice(idx, 4), out int lowSurrogate, out bytesConsumed, 'x');
Debug.Assert(result);
Debug.Assert(bytesConsumed == 4);
Expand Down
Loading