TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec;
- string clrPropertyName = memberMetadata.ClrName;
+ string nameSpecifiedInSourceCode = memberMetadata.NameSpecifiedInSourceCode;
string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef;
string getterValue = memberMetadata switch
{
{ DefaultIgnoreCondition: JsonIgnoreCondition.Always } => "null",
- { CanUseGetter: true } => $"static (obj) => (({declaringTypeCompilableName})obj).{clrPropertyName}{(memberMetadata.TypeGenerationSpec.CanContainNullableReferenceAnnotations ? "!" : "")}",
+ { CanUseGetter: true } => $"static (obj) => (({declaringTypeCompilableName})obj).{nameSpecifiedInSourceCode}{(memberMetadata.TypeGenerationSpec.CanContainNullableReferenceAnnotations ? "!" : "")}",
{ CanUseGetter: false, HasJsonInclude: true }
- => @$"static (obj) => throw new {InvalidOperationExceptionTypeRef}(""{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.Type.Name, memberMetadata.ClrName)}"")",
+ => @$"static (obj) => throw new {InvalidOperationExceptionTypeRef}(""{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.Type.Name, nameSpecifiedInSourceCode)}"")",
_ => "null"
};
{ CanUseSetter: true, IsInitOnlySetter: true }
=> @$"static (obj, value) => throw new {InvalidOperationExceptionTypeRef}(""{ExceptionMessages.InitOnlyPropertyDeserializationNotSupported}"")",
{ CanUseSetter: true } when typeGenerationSpec.IsValueType
- => $@"static (obj, value) => {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value!",
+ => $@"static (obj, value) => {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{nameSpecifiedInSourceCode} = value!",
{ CanUseSetter: true }
- => @$"static (obj, value) => (({declaringTypeCompilableName})obj).{clrPropertyName} = value!",
+ => @$"static (obj, value) => (({declaringTypeCompilableName})obj).{nameSpecifiedInSourceCode} = value!",
{ CanUseSetter: false, HasJsonInclude: true }
=> @$"static (obj, value) => throw new {InvalidOperationExceptionTypeRef}(""{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.Type.Name, memberMetadata.ClrName)}"")",
_ => "null",
HasJsonInclude = {FormatBool(memberMetadata.HasJsonInclude)},
IsExtensionData = {FormatBool(memberMetadata.IsExtensionData)},
NumberHandling = {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
- PropertyName = ""{clrPropertyName}"",
+ PropertyName = ""{memberMetadata.ClrName}"",
JsonPropertyName = {jsonPropertyNameValue}
}};
Type propertyType = propertyTypeSpec.Type;
string? objectRef = castingRequiredForProps ? $"(({propertyGenSpec.DeclaringTypeRef}){ValueVarName})" : ValueVarName;
- string propValue = $"{objectRef}.{propertyGenSpec.ClrName}";
+ string propValue = $"{objectRef}.{propertyGenSpec.NameSpecifiedInSourceCode}";
string methodArgs = $"{propVarName}, {propValue}";
string? methodToCall = GetWriterMethod(propertyType);
out bool setterIsVirtual,
out bool setterIsInitOnly);
+ bool needsAtSign = memberInfo switch
+ {
+ PropertyInfoWrapper prop => prop.NeedsAtSign,
+ FieldInfoWrapper field => field.NeedsAtSign,
+ _ => false
+ };
+
string clrName = memberInfo.Name;
string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy);
string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName);
return new PropertyGenerationSpec
{
- ClrName = clrName,
+ NameSpecifiedInSourceCode = needsAtSign ? "@" + memberInfo.Name : memberInfo.Name,
+ ClrName = memberInfo.Name,
IsProperty = memberInfo.MemberType == MemberTypes.Property,
IsPublic = isPublic,
IsVirtual = isVirtual,
[DebuggerDisplay("Name={Name}, Type={TypeMetadata}")]
internal sealed class PropertyGenerationSpec
{
+ /// <summary>
+ /// The exact name specified in the source code. This might be different
+ /// from the <see cref="ClrName"/> because source code might be decorated
+ /// with '@' for reserved keywords, e.g. public string @event { get; set; }
+ /// </summary>
+ public string NameSpecifiedInSourceCode { get; init; }
+
public string ClrName { get; init; }
/// <summary>
using System.Reflection;
using Microsoft.CodeAnalysis;
using System.Globalization;
+using Microsoft.CodeAnalysis.CSharp;
namespace System.Text.Json.Reflection
{
{
_field = parameter;
_metadataLoadContext = metadataLoadContext;
+
+ NeedsAtSign = SyntaxFacts.GetKeywordKind(_field.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(_field.Name) != SyntaxKind.None;
}
private FieldAttributes? _attributes;
public override string Name => _field.Name;
+ public bool NeedsAtSign { get; }
+
public override Type ReflectedType => throw new NotImplementedException();
public override object[] GetCustomAttributes(bool inherit)
using System.Globalization;
using System.Reflection;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
namespace System.Text.Json.Reflection
{
internal sealed class PropertyInfoWrapper : PropertyInfo
{
private readonly IPropertySymbol _property;
- private MetadataLoadContextInternal _metadataLoadContext;
+ private readonly MetadataLoadContextInternal _metadataLoadContext;
public PropertyInfoWrapper(IPropertySymbol property, MetadataLoadContextInternal metadataLoadContext)
{
_property = property;
_metadataLoadContext = metadataLoadContext;
+
+ NeedsAtSign = SyntaxFacts.GetKeywordKind(_property.Name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(_property.Name) != SyntaxKind.None;
}
public override PropertyAttributes Attributes => throw new NotImplementedException();
public override string Name => _property.Name;
+ public bool NeedsAtSign { get; }
+
public override Type ReflectedType => throw new NotImplementedException();
public override MethodInfo[] GetAccessors(bool nonPublic)
}
[Fact]
+ public static void SupportsReservedLanguageKeywordsAsProperties()
+ {
+ GreetingCard card = new()
+ {
+ @event = "Birthday",
+ message = @"Happy Birthday!"
+ };
+
+ byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(card, GreetingCardJsonContext.Default.GreetingCard);
+
+ card = JsonSerializer.Deserialize<GreetingCard>(utf8Json, GreetingCardJsonContext.Default.GreetingCard);
+ Assert.Equal("Birthday", card.@event);
+ Assert.Equal("Happy Birthday!", card.message);
+ }
+
+ [Fact]
+ public static void SupportsReservedLanguageKeywordsAsFields()
+ {
+ var options = new JsonSerializerOptions { IncludeFields = true };
+
+ GreetingCardWithFields card = new() {@event = "Birthday", message = @"Happy Birthday!"};
+
+ byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(card, GreetingCardWithFieldsJsonContext.Default.GreetingCardWithFields);
+
+ card = JsonSerializer.Deserialize<GreetingCardWithFields>(utf8Json, GreetingCardWithFieldsJsonContext.Default.GreetingCardWithFields);
+ Assert.Equal("Happy Birthday!", card.message);
+ Assert.Equal("Birthday", card.@event);
+ }
+
+ [Fact]
public static void SupportsPositionalRecords()
{
Person person = new(FirstName: "Jane", LastName: "Doe");
{
}
+ internal class GreetingCard
+ {
+ public string @event { get;set; }
+ public string message { get;set; }
+ }
+
+ internal class GreetingCardWithFields
+ {
+ public string @event;
+ public string message;
+ }
+
+ [JsonSerializable(typeof(GreetingCard))]
+ internal partial class GreetingCardJsonContext : JsonSerializerContext
+ {
+ }
+
+ [JsonSourceGenerationOptions(IncludeFields = true)]
+ [JsonSerializable(typeof(GreetingCardWithFields))]
+ internal partial class GreetingCardWithFieldsJsonContext : JsonSerializerContext
+ {
+ }
+
// Regression test for https://github.com/dotnet/runtime/issues/62079
[Fact]
public static void SupportsPropertiesWithCustomConverterFactory()
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>());
- CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
}
[Fact]
receivedFields = fields.ToArray();
receivedProperties = props.ToArray();
}
-
+
string[] receivedMethods = type.GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray();
Array.Sort(receivedFields);
[Fact]
public static void NoWarningsDueToObsoleteMembers()
{
- string source = @"using System;
+ string source = @"using System;
using System.Text.Json.Serialization;
namespace Test
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>());
CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
}
+
+ [Fact]
+ public static void NoErrorsWhenUsingReservedCSharpKeywords()
+ {
+ string source = @"using System;
+using System.Text.Json.Serialization;
+
+namespace Test
+{
+ [JsonSerializable(typeof(ClassWithPropertiesAndFieldsThatAreReservedKeywords))]
+ public partial class JsonContext : JsonSerializerContext { }
+
+ public class ClassWithPropertiesAndFieldsThatAreReservedKeywords
+ {
+ public int @class;
+ public string @event { get; set; }
+ }
+}
+";
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGenerator generator = new JsonSourceGenerator();
+
+ Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out _, generator);
+ ImmutableArray<Diagnostic> generatorDiags = newCompilation.GetDiagnostics();
+
+ // No diagnostics expected.
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>());
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>());
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
+ }
+
+ [Fact]
+ public static void NoErrorsWhenUsingIgnoredReservedCSharpKeywords()
+ {
+ string source = @"using System;
+using System.Text.Json.Serialization;
+
+namespace Test
+{
+ [JsonSerializable(typeof(ClassWithPropertyNameThatIsAReservedKeyword))]
+ public partial class JsonContext : JsonSerializerContext { }
+
+ public class ClassWithPropertyNameThatIsAReservedKeyword
+ {
+ [JsonIgnore]
+ public string @event { get; set; }
+ }
+}
+";
+ Compilation compilation = CompilationHelper.CreateCompilation(source);
+ JsonSourceGenerator generator = new JsonSourceGenerator();
+
+ Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out _, generator);
+ ImmutableArray<Diagnostic> generatorDiags = newCompilation.GetDiagnostics();
+
+ // No diagnostics expected.
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>());
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>());
+ CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>());
+ }
}
}