From 96820db7f589c3d0f18ca08cf71f4f1b2217d009 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 5 Jul 2023 22:37:20 +0100 Subject: [PATCH 1/2] Relax JsonIncludeAttribute to also support private or internal members. --- .../gen/JsonSourceGenerator.Parser.cs | 104 +++++++++--------- .../src/Resources/Strings.resx | 4 +- .../DefaultJsonTypeInfoResolver.Helpers.cs | 71 +++++------- .../Metadata/JsonMetadataServices.Helpers.cs | 2 +- .../Metadata/JsonPropertyInfo.cs | 2 +- .../Metadata/JsonPropertyInfoOfT.cs | 5 +- .../Text/Json/ThrowHelper.Serialization.cs | 4 +- .../tests/Common/JsonSerializerWrapper.cs | 2 + ...pertyVisibilityTests.NonPublicAccessors.cs | 62 +++++++---- .../Serialization/PropertyVisibilityTests.cs | 17 --- .../CompilationHelper.cs | 6 + .../JsonSourceGeneratorDiagnosticsTests.cs | 6 +- 12 files changed, 142 insertions(+), 143 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index d0641ac140cc8..6e8ede42a7bb7 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -849,7 +849,7 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type) memberInfo, hasJsonInclude, out bool isReadOnly, - out bool isPublic, + out bool isAccessible, out bool isRequired, out bool canUseGetter, out bool canUseSetter, @@ -899,7 +899,7 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type) NameSpecifiedInSourceCode = memberInfo.MemberNameNeedsAtSign() ? "@" + memberInfo.Name : memberInfo.Name, MemberName = memberInfo.Name, IsProperty = memberInfo is IPropertySymbol, - IsPublic = isPublic, + IsPublic = isAccessible, IsVirtual = memberInfo.IsVirtual(), JsonPropertyName = jsonPropertyName, RuntimePropertyName = runtimePropertyName, @@ -1031,14 +1031,14 @@ private void ProcessMember( ISymbol memberInfo, bool hasJsonInclude, out bool isReadOnly, - out bool isPublic, + out bool isAccessible, out bool isRequired, out bool canUseGetter, out bool canUseSetter, out bool hasJsonIncludeButIsInaccessible, out bool isSetterInitOnly) { - isPublic = false; + isAccessible = false; isReadOnly = false; isRequired = false; canUseGetter = false; @@ -1049,72 +1049,72 @@ private void ProcessMember( switch (memberInfo) { case IPropertySymbol propertyInfo: - { - IMethodSymbol? getMethod = propertyInfo.GetMethod; - IMethodSymbol? setMethod = propertyInfo.SetMethod; #if ROSLYN4_4_OR_GREATER - isRequired = propertyInfo.IsRequired; + isRequired = propertyInfo.IsRequired; #endif - - if (getMethod != null) + if (propertyInfo.GetMethod is { } getMethod) + { + if (getMethod.DeclaredAccessibility is Accessibility.Public) { - if (getMethod.DeclaredAccessibility is Accessibility.Public) - { - isPublic = true; - canUseGetter = true; - } - else if (IsSymbolAccessibleWithin(getMethod, within: contextType)) - { - canUseGetter = hasJsonInclude; - } - else - { - hasJsonIncludeButIsInaccessible = hasJsonInclude; - } + isAccessible = true; + canUseGetter = true; } - - if (setMethod != null) + else if (IsSymbolAccessibleWithin(getMethod, within: contextType)) { - isSetterInitOnly = setMethod.IsInitOnly; - - if (setMethod.DeclaredAccessibility is Accessibility.Public) - { - isPublic = true; - canUseSetter = true; - } - else if (IsSymbolAccessibleWithin(setMethod, within: contextType)) - { - canUseSetter = hasJsonInclude; - } - else - { - hasJsonIncludeButIsInaccessible = hasJsonInclude; - } + isAccessible = true; + canUseGetter = hasJsonInclude; } else { - isReadOnly = true; + hasJsonIncludeButIsInaccessible = hasJsonInclude; } } - break; - case IFieldSymbol fieldInfo: + + if (propertyInfo.SetMethod is { } setMethod) { - isReadOnly = fieldInfo.IsReadOnly; -#if ROSLYN4_4_OR_GREATER - isRequired = fieldInfo.IsRequired; -#endif - if (fieldInfo.DeclaredAccessibility is Accessibility.Public) + isSetterInitOnly = setMethod.IsInitOnly; + + if (setMethod.DeclaredAccessibility is Accessibility.Public) { - isPublic = true; - canUseGetter = true; - canUseSetter = !isReadOnly; + isAccessible = true; + canUseSetter = true; + } + else if (IsSymbolAccessibleWithin(setMethod, within: contextType)) + { + isAccessible = true; + canUseSetter = hasJsonInclude; } else { - // Unlike properties JsonIncludeAttribute is not supported for internal fields. hasJsonIncludeButIsInaccessible = hasJsonInclude; } } + else + { + isReadOnly = true; + } + break; + case IFieldSymbol fieldInfo: + isReadOnly = fieldInfo.IsReadOnly; +#if ROSLYN4_4_OR_GREATER + isRequired = fieldInfo.IsRequired; +#endif + if (fieldInfo.DeclaredAccessibility is Accessibility.Public) + { + isAccessible = true; + canUseGetter = true; + canUseSetter = !isReadOnly; + } + else if (IsSymbolAccessibleWithin(fieldInfo, within: contextType)) + { + isAccessible = true; + canUseGetter = hasJsonInclude; + canUseSetter = hasJsonInclude && !isReadOnly; + } + else + { + hasJsonIncludeButIsInaccessible = hasJsonInclude; + } break; default: Debug.Fail("Method given an invalid symbol type."); diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 064094dac3a4d..31b12248015bd 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -507,8 +507,8 @@ Serialization and deserialization of '{0}' instances is not supported. - - The non-public property '{0}' on type '{1}' is annotated with 'JsonIncludeAttribute' which is invalid. + + The property '{0}' on type '{1}' which is annotated with 'JsonIncludeAttribute' is not accesible by the source generator. The type '{0}' of property '{1}' on type '{2}' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs index 260f55e6629db..47a2b9585568c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs @@ -132,52 +132,35 @@ private static void AddMembersDeclaredBySuperType( continue; } - // For now we only support public properties (i.e. setter and/or getter is public). + bool hasJsonIncludeAttribute = propertyInfo.GetCustomAttribute(inherit: false) != null; + + // Only include properties that either have a public getter or a public setter or have the JsonIncludeAttribute set. if (propertyInfo.GetMethod?.IsPublic == true || - propertyInfo.SetMethod?.IsPublic == true) + propertyInfo.SetMethod?.IsPublic == true || + hasJsonIncludeAttribute) { AddMember( typeInfo, typeToConvert: propertyInfo.PropertyType, memberInfo: propertyInfo, shouldCheckMembersForRequiredMemberAttribute, + hasJsonIncludeAttribute, ref state); } - else - { - if (propertyInfo.GetCustomAttribute(inherit: false) != null) - { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo.Name, currentType); - } - - // Non-public properties should not be included for (de)serialization. - } } foreach (FieldInfo fieldInfo in currentType.GetFields(BindingFlags)) { - bool hasJsonInclude = fieldInfo.GetCustomAttribute(inherit: false) != null; - - if (fieldInfo.IsPublic) - { - if (hasJsonInclude || typeInfo.Options.IncludeFields) - { - AddMember( - typeInfo, - typeToConvert: fieldInfo.FieldType, - memberInfo: fieldInfo, - shouldCheckMembersForRequiredMemberAttribute, - ref state); - } - } - else + bool hasJsonIncludeAtribute = fieldInfo.GetCustomAttribute(inherit: false) != null; + if (hasJsonIncludeAtribute || (fieldInfo.IsPublic && typeInfo.Options.IncludeFields)) { - if (hasJsonInclude) - { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldInfo.Name, currentType); - } - - // Non-public fields should not be included for (de)serialization. + AddMember( + typeInfo, + typeToConvert: fieldInfo.FieldType, + memberInfo: fieldInfo, + shouldCheckMembersForRequiredMemberAttribute, + hasJsonIncludeAtribute, + ref state); } } } @@ -189,9 +172,10 @@ private static void AddMember( Type typeToConvert, MemberInfo memberInfo, bool shouldCheckForRequiredKeyword, + bool hasJsonIncludeAttribute, ref JsonTypeInfo.PropertyHierarchyResolutionState state) { - JsonPropertyInfo? jsonPropertyInfo = CreatePropertyInfo(typeInfo, typeToConvert, memberInfo, typeInfo.Options, shouldCheckForRequiredKeyword); + JsonPropertyInfo? jsonPropertyInfo = CreatePropertyInfo(typeInfo, typeToConvert, memberInfo, typeInfo.Options, shouldCheckForRequiredKeyword, hasJsonIncludeAttribute); if (jsonPropertyInfo == null) { // ignored invalid property @@ -209,7 +193,8 @@ private static void AddMember( Type typeToConvert, MemberInfo memberInfo, JsonSerializerOptions options, - bool shouldCheckForRequiredKeyword) + bool shouldCheckForRequiredKeyword, + bool hasJsonIncludeAttribute) { JsonIgnoreCondition? ignoreCondition = memberInfo.GetCustomAttribute(inherit: false)?.Condition; @@ -234,7 +219,7 @@ private static void AddMember( } JsonPropertyInfo jsonPropertyInfo = typeInfo.CreatePropertyUsingReflection(typeToConvert); - PopulatePropertyInfo(jsonPropertyInfo, memberInfo, customConverter, ignoreCondition, shouldCheckForRequiredKeyword); + PopulatePropertyInfo(jsonPropertyInfo, memberInfo, customConverter, ignoreCondition, shouldCheckForRequiredKeyword, hasJsonIncludeAttribute); return jsonPropertyInfo; } @@ -299,7 +284,13 @@ private static void PopulateParameterInfoValues(JsonTypeInfo typeInfo) [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] - private static void PopulatePropertyInfo(JsonPropertyInfo jsonPropertyInfo, MemberInfo memberInfo, JsonConverter? customConverter, JsonIgnoreCondition? ignoreCondition, bool shouldCheckForRequiredKeyword) + private static void PopulatePropertyInfo( + JsonPropertyInfo jsonPropertyInfo, + MemberInfo memberInfo, + JsonConverter? customConverter, + JsonIgnoreCondition? ignoreCondition, + bool shouldCheckForRequiredKeyword, + bool hasJsonIncludeAttribute) { Debug.Assert(jsonPropertyInfo.AttributeProvider == null); @@ -326,7 +317,7 @@ private static void PopulatePropertyInfo(JsonPropertyInfo jsonPropertyInfo, Memb if (ignoreCondition != JsonIgnoreCondition.Always) { - jsonPropertyInfo.DetermineReflectionPropertyAccessors(memberInfo); + jsonPropertyInfo.DetermineReflectionPropertyAccessors(memberInfo, useNonPublicAccessors: hasJsonIncludeAttribute); } jsonPropertyInfo.IgnoreCondition = ignoreCondition; @@ -379,15 +370,13 @@ private static void DeterminePropertyIsRequired(JsonPropertyInfo propertyInfo, M [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] - internal static void DeterminePropertyAccessors(JsonPropertyInfo jsonPropertyInfo, MemberInfo memberInfo) + internal static void DeterminePropertyAccessors(JsonPropertyInfo jsonPropertyInfo, MemberInfo memberInfo, bool useNonPublicAccessors) { Debug.Assert(memberInfo is FieldInfo or PropertyInfo); switch (memberInfo) { case PropertyInfo propertyInfo: - bool useNonPublicAccessors = propertyInfo.GetCustomAttribute(inherit: false) != null; - MethodInfo? getMethod = propertyInfo.GetMethod; if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors)) { @@ -403,7 +392,7 @@ internal static void DeterminePropertyAccessors(JsonPropertyInfo jsonPrope break; case FieldInfo fieldInfo: - Debug.Assert(fieldInfo.IsPublic); + Debug.Assert(fieldInfo.IsPublic || useNonPublicAccessors); jsonPropertyInfo.Get = MemberAccessor.CreateFieldGetter(fieldInfo); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs index 1f9465ff4a28a..1573bdba0c6eb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs @@ -146,7 +146,7 @@ internal static void PopulateProperties(JsonTypeInfo typeInfo, JsonTypeInfo.Json if (jsonPropertyInfo.SrcGen_HasJsonInclude) { Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen"); - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType); + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnInaccessibleProperty(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType); } continue; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index 40edacad629e7..a2a65769868a6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -355,7 +355,7 @@ internal void Configure() [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] - internal abstract void DetermineReflectionPropertyAccessors(MemberInfo memberInfo); + internal abstract void DetermineReflectionPropertyAccessors(MemberInfo memberInfo, bool useNonPublicAccessors); private void CacheNameAsUtf8BytesAndEscapedNameSection() { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index 6c2807ae45cd3..379a3754cf574 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -128,8 +128,9 @@ internal override JsonParameterInfo CreateJsonParameterInfo(JsonParameterInfoVal [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] - internal override void DetermineReflectionPropertyAccessors(MemberInfo memberInfo) - => DefaultJsonTypeInfoResolver.DeterminePropertyAccessors(this, memberInfo); + internal override void DetermineReflectionPropertyAccessors(MemberInfo memberInfo, bool useNonPublicAccessors) + => DefaultJsonTypeInfoResolver.DeterminePropertyAccessors(this, memberInfo, useNonPublicAccessors); + private protected override void DetermineEffectiveConverter(JsonTypeInfo jsonTypeInfo) { Debug.Assert(jsonTypeInfo is JsonTypeInfo); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 09951718f555f..fc34a376418f7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -341,9 +341,9 @@ public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorP } [DoesNotReturn] - public static void ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(string memberName, Type declaringType) + public static void ThrowInvalidOperationException_JsonIncludeOnInaccessibleProperty(string memberName, Type declaringType) { - throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnNonPublicInvalid, memberName, declaringType)); + throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnInaccessibleProperty, memberName, declaringType)); } [DoesNotReturn] diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs index 5b269f827c284..27c7541522211 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonSerializerWrapper.cs @@ -17,6 +17,8 @@ public abstract partial class JsonSerializerWrapper /// public abstract JsonSerializerOptions DefaultOptions { get; } + public bool IsSourceGeneratedSerializer => DefaultOptions.TypeInfoResolver is JsonSerializerContext; + /// /// Do the deserialize methods allow a value of 'null'. /// For example, deserializing JSON to a String supports null by returning a 'null' String reference from a literal value of "null". diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs index 39cd1a14eea16..cf19348479ecd 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Xunit; @@ -317,28 +318,43 @@ public class ClassWithMixedPropertyAccessors_PropertyAttributes } [Theory] - [InlineData(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithInternalProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithPrivateField_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithInternalField_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithProtectedField_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty))] - public virtual async Task NonPublicProperty_WithJsonInclude_Invalid(Type type) - { - InvalidOperationException ex = await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("{}", type)); - string exAsStr = ex.ToString(); - Assert.Contains("MyString", exAsStr); - Assert.Contains(type.ToString(), exAsStr); - Assert.Contains("JsonIncludeAttribute", exAsStr); - - ex = await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(Activator.CreateInstance(type), type)); - exAsStr = ex.ToString(); - Assert.Contains("MyString", exAsStr); - Assert.Contains(type.ToString(), exAsStr); - Assert.Contains("JsonIncludeAttribute", exAsStr); + [InlineData(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty), false)] + [InlineData(typeof(ClassWithInternalProperty_WithJsonIncludeProperty), true)] + [InlineData(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty), false)] + [InlineData(typeof(ClassWithPrivateField_WithJsonIncludeProperty), false)] + [InlineData(typeof(ClassWithInternalField_WithJsonIncludeProperty), true)] + [InlineData(typeof(ClassWithProtectedField_WithJsonIncludeProperty), false)] + [InlineData(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty), false)] + [InlineData(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty), true)] + [InlineData(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty), false)] + public virtual async Task NonPublicProperty_JsonInclude_WorksAsExpected(Type type, bool isAccessibleBySourceGen) + { + if (!Serializer.IsSourceGeneratedSerializer || isAccessibleBySourceGen) + { + string json = """{"MyString":"value"}"""; + MemberInfo memberInfo = type.GetMember("MyString", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; + + object result = await Serializer.DeserializeWrapper("""{"MyString":"value"}""", type); + Assert.IsType(type, result); + Assert.Equal(memberInfo is PropertyInfo p ? p.GetValue(result) : ((FieldInfo)memberInfo).GetValue(result), "value"); + + string actualJson = await Serializer.SerializeWrapper(result, type); + Assert.Equal(json, actualJson); + } + else + { + InvalidOperationException ex = await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("{}", type)); + string exAsStr = ex.ToString(); + Assert.Contains("MyString", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIncludeAttribute", exAsStr); + + ex = await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(Activator.CreateInstance(type), type)); + exAsStr = ex.ToString(); + Assert.Contains("MyString", exAsStr); + Assert.Contains(type.ToString(), exAsStr); + Assert.Contains("JsonIncludeAttribute", exAsStr); + } } public class ClassWithPrivateProperty_WithJsonIncludeProperty @@ -350,7 +366,7 @@ public class ClassWithPrivateProperty_WithJsonIncludeProperty public class ClassWithInternalProperty_WithJsonIncludeProperty { [JsonInclude] - internal string MyString { get; } + internal string MyString { get; set; } } public class ClassWithProtectedProperty_WithJsonIncludeProperty diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs index 27b319ba8d895..2baf054c426bf 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -348,23 +348,6 @@ public PropertyVisibilityTests_Default() { } - [Theory] - [InlineData(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithInternalProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithPrivateField_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithInternalField_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithProtectedField_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithPrivate_InitOnlyProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithInternal_InitOnlyProperty_WithJsonIncludeProperty))] - [InlineData(typeof(ClassWithProtected_InitOnlyProperty_WithJsonIncludeProperty))] - public override async Task NonPublicProperty_WithJsonInclude_Invalid(Type type) - { - // Exception messages direct users to use JsonSourceGenerationMode.Metadata to see a more detailed error. - await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("{}", type)); - await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(Activator.CreateInstance(type), type)); - } - [Theory] [InlineData(typeof(ClassWithBadIgnoreAttribute))] [InlineData(typeof(StructWithBadIgnoreAttribute))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index 67974b94564c4..0d10c217fab26 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -491,6 +491,12 @@ public class Location public string PhoneNumber { internal get; set; } [JsonInclude] public string Country { private get; set; } + [JsonInclude] + internal string InternalProperty { get; set; } + [JsonInclude] + internal string InternalPropertyWithPrivateGetter { private get; set; } + [JsonInclude] + internal string InternalPropertyWithPrivateSetter { get; private set; } public int GetPrivateField() => privateField; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs index a89d83ef49793..d40df53bc3e0d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -270,16 +270,18 @@ public void WarnOnClassesWithInaccessibleJsonIncludeProperties() Location idLocation = compilation.GetSymbolsWithName("Id").First().Locations[0]; Location address2Location = compilation.GetSymbolsWithName("Address2").First().Locations[0]; Location countryLocation = compilation.GetSymbolsWithName("Country").First().Locations[0]; - Location internalFieldLocation = compilation.GetSymbolsWithName("internalField").First().Locations[0]; Location privateFieldLocation = compilation.GetSymbolsWithName("privateField").First().Locations[0]; + Location internalPropertyWithPrivateGetterLocation = compilation.GetSymbolsWithName("InternalPropertyWithPrivateGetter").First().Locations[0]; + Location internalPropertyWithPrivateSetterLocation = compilation.GetSymbolsWithName("InternalPropertyWithPrivateSetter").First().Locations[0]; var expectedDiagnostics = new DiagnosticData[] { new(DiagnosticSeverity.Warning, idLocation, "The member 'Location.Id' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, address2Location, "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), - new(DiagnosticSeverity.Warning, internalFieldLocation, "The member 'Location.internalField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, privateFieldLocation, "The member 'Location.privateField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, internalPropertyWithPrivateGetterLocation, "The member 'Location.InternalPropertyWithPrivateGetter' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, internalPropertyWithPrivateSetterLocation, "The member 'Location.InternalPropertyWithPrivateSetter' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), }; CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); From baee5dba187329c534195c29a1d8c8cf55143962 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 5 Jul 2023 23:58:47 +0100 Subject: [PATCH 2/2] Relax JsonConstructorAttribute to include internal or private constructors (where applicable). --- docs/project/list-of-diagnostics.md | 2 +- .../Common/ReflectionExtensions.cs | 11 ++--- ...onSourceGenerator.DiagnosticDescriptors.cs | 8 ++++ .../gen/JsonSourceGenerator.Parser.cs | 28 +++++------ .../gen/Resources/Strings.resx | 6 +++ .../gen/Resources/xlf/Strings.cs.xlf | 10 ++++ .../gen/Resources/xlf/Strings.de.xlf | 10 ++++ .../gen/Resources/xlf/Strings.es.xlf | 10 ++++ .../gen/Resources/xlf/Strings.fr.xlf | 10 ++++ .../gen/Resources/xlf/Strings.it.xlf | 10 ++++ .../gen/Resources/xlf/Strings.ja.xlf | 10 ++++ .../gen/Resources/xlf/Strings.ko.xlf | 10 ++++ .../gen/Resources/xlf/Strings.pl.xlf | 10 ++++ .../gen/Resources/xlf/Strings.pt-BR.xlf | 10 ++++ .../gen/Resources/xlf/Strings.ru.xlf | 10 ++++ .../gen/Resources/xlf/Strings.tr.xlf | 10 ++++ .../gen/Resources/xlf/Strings.zh-Hans.xlf | 10 ++++ .../gen/Resources/xlf/Strings.zh-Hant.xlf | 10 ++++ .../Metadata/ReflectionEmitMemberAccessor.cs | 2 +- .../ConstructorTests.AttributePresence.cs | 39 +++++++++------ ...m.Text.Json.SourceGeneration.Tests.targets | 3 +- .../CompilationHelper.cs | 48 +++++++++++++++++++ .../JsonSourceGeneratorDiagnosticsTests.cs | 25 +++++++++- 23 files changed, 263 insertions(+), 39 deletions(-) diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index e5ea1f587da07..5bdbbd518caac 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -256,7 +256,7 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | __`SYSLIB1219`__ | *_`SYSLIB1214`-`SYSLIB1219` reserved for Microsoft.Extensions.Options.SourceGeneration.* | | __`SYSLIB1220`__ | JsonSourceGenerator encountered a [JsonConverterAttribute] with an invalid type argument. | | __`SYSLIB1221`__ | JsonSourceGenerator does not support this C# language version. | -| __`SYSLIB1222`__ | *`SYSLIB1220`-`SYSLIB229` reserved for System.Text.Json.SourceGeneration.* | +| __`SYSLIB1222`__ | Constructor annotated with JsonConstructorAttribute is inaccessible. | | __`SYSLIB1223`__ | *`SYSLIB1220`-`SYSLIB229` reserved for System.Text.Json.SourceGeneration.* | | __`SYSLIB1224`__ | *`SYSLIB1220`-`SYSLIB229` reserved for System.Text.Json.SourceGeneration.* | | __`SYSLIB1225`__ | *`SYSLIB1220`-`SYSLIB229` reserved for System.Text.Json.SourceGeneration.* | diff --git a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs index 0fae60f3dd97b..3933535e35936 100644 --- a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs @@ -259,21 +259,18 @@ public static bool TryGetDeserializationConstructor( } } - // For correctness, throw if multiple ctors have [JsonConstructor], even if one or more are non-public. - ConstructorInfo? dummyCtorWithAttribute = ctorWithAttribute; - - constructors = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); - foreach (ConstructorInfo constructor in constructors) + // Search for non-public ctors with [JsonConstructor]. + foreach (ConstructorInfo constructor in type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)) { if (HasJsonConstructorAttribute(constructor)) { - if (dummyCtorWithAttribute != null) + if (ctorWithAttribute != null) { deserializationCtor = null; return false; } - dummyCtorWithAttribute = constructor; + ctorWithAttribute = constructor; } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs index 731ecba4302e2..95e6f1e5502e9 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -98,6 +98,14 @@ internal static class DiagnosticDescriptors category: JsonConstants.SystemTextJsonSourceGenerationName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static DiagnosticDescriptor JsonConstructorInaccessible { get; } = new DiagnosticDescriptor( + id: "SYSLIB1222", + title: new LocalizableResourceString(nameof(SR.JsonConstructorInaccessibleTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + messageFormat: new LocalizableResourceString(nameof(SR.JsonConstructorInaccessibleMessageFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), + category: JsonConstants.SystemTextJsonSourceGenerationName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); } } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 6e8ede42a7bb7..bc0be7a3a7926 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -443,20 +443,22 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener if (!TryGetDeserializationConstructor(type, useDefaultCtorInAnnotatedStructs, out IMethodSymbol? constructor)) { - classType = ClassType.TypeUnsupportedBySourceGen; ReportDiagnostic(DiagnosticDescriptors.MultipleJsonConstructorAttribute, typeLocation, type.ToDisplayString()); } - else + else if (constructor != null && !IsSymbolAccessibleWithin(constructor, within: contextType)) { - classType = ClassType.Object; + ReportDiagnostic(DiagnosticDescriptors.JsonConstructorInaccessible, typeLocation, type.ToDisplayString()); + constructor = null; + } - implementsIJsonOnSerializing = _knownSymbols.IJsonOnSerializingType.IsAssignableFrom(type); - implementsIJsonOnSerialized = _knownSymbols.IJsonOnSerializedType.IsAssignableFrom(type); + classType = ClassType.Object; - ctorParamSpecs = ParseConstructorParameters(typeToGenerate, constructor, out constructionStrategy, out constructorSetsRequiredMembers); - propertySpecs = ParsePropertyGenerationSpecs(contextType, typeToGenerate, typeLocation, options, out hasExtensionDataProperty); - propertyInitializerSpecs = ParsePropertyInitializers(ctorParamSpecs, propertySpecs, constructorSetsRequiredMembers, ref constructionStrategy); - } + implementsIJsonOnSerializing = _knownSymbols.IJsonOnSerializingType.IsAssignableFrom(type); + implementsIJsonOnSerialized = _knownSymbols.IJsonOnSerializedType.IsAssignableFrom(type); + + ctorParamSpecs = ParseConstructorParameters(typeToGenerate, constructor, out constructionStrategy, out constructorSetsRequiredMembers); + propertySpecs = ParsePropertyGenerationSpecs(contextType, typeToGenerate, typeLocation, options, out hasExtensionDataProperty); + propertyInitializerSpecs = ParsePropertyInitializers(ctorParamSpecs, propertySpecs, constructorSetsRequiredMembers, ref constructionStrategy); } var typeRef = new TypeRef(type); @@ -1410,20 +1412,18 @@ private bool TryGetDeserializationConstructor( } } - // For correctness, throw if multiple ctors have [JsonConstructor], even if one or more are non-public. - IMethodSymbol? dummyCtorWithAttribute = ctorWithAttribute; - + // Search for non-public ctors with [JsonConstructor]. foreach (IMethodSymbol constructor in namedType.GetExplicitlyDeclaredInstanceConstructors().Where(ctor => ctor.DeclaredAccessibility is not Accessibility.Public)) { if (constructor.ContainsAttribute(_knownSymbols.JsonConstructorAttributeType)) { - if (dummyCtorWithAttribute != null) + if (ctorWithAttribute != null) { deserializationCtor = null; return false; } - dummyCtorWithAttribute = constructor; + ctorWithAttribute = constructor; } } diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index 300e683ab4e70..baa0c88a79043 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -189,4 +189,10 @@ The System.Text.Json source generator is not available in C# '{0}'. Please use language version {1} or greater. + + Constructor annotated with JsonConstructorAttribute is inaccessible. + + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf index 7a59d88f9eb4b..af105417ad8ef 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.cs.xlf @@ -62,6 +62,16 @@ Deserializace vlastností pouze pro inicializaci se v současnosti v režimu generování zdroje nepodporuje. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. Typ JsonConverterAttribute {0} specifikovaný u členu {1} není typem konvertoru nebo neobsahuje přístupný konstruktor bez parametrů. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf index 7fa9bc1f1a21d..8a10d9fbab3f9 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.de.xlf @@ -62,6 +62,16 @@ Die Deserialisierung von reinen init-Eigenschaften wird im Quellgenerierungsmodus derzeit nicht unterstützt. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. Der für den Member "{1}" angegebene JsonConverterAttribute-Typ "{0}" ist kein Konvertertyp oder enthält keinen parameterlosen Konstruktor, auf den zugegriffen werden kann. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf index ad1d5dcc7ba90..c59fa4f8c38e6 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.es.xlf @@ -62,6 +62,16 @@ Actualmente no se admite la deserialización de propiedades de solo inicialización en el modo de generación de origen. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. El tipo “JsonConverterAttribute” “{0}” especificado en el miembro “{1}” no es un tipo de convertidor o no contiene un constructor sin parámetros accesible. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf index bad19743c054c..ec15ea9e30458 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.fr.xlf @@ -62,6 +62,16 @@ La désérialisation des propriétés d’initialisation uniquement n’est actuellement pas prise en charge en mode de génération de source. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. Le type 'JsonConverterAttribute' '{0}' spécifié sur le membre '{1}' n’est pas un type convertisseur ou ne contient pas de constructeur sans paramètre accessible. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf index 01a6c7ca95f77..42f70a8a586e6 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.it.xlf @@ -62,6 +62,16 @@ La deserializzazione delle proprietà di sola inizializzazione al momento non è supportata nella modalità di generazione di origine. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. Il tipo 'JsonConverterAttribute' '{0}' specificato nel membro '{1}' non è un tipo di convertitore o non contiene un costruttore senza parametri accessibile. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf index 5a4a1e616dbe2..ac45f319fe035 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ja.xlf @@ -62,6 +62,16 @@ 現在、ソース生成モードでは init-only プロパティの逆シリアル化はサポートされていません。 + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. メンバー '{1}' で指定されている 'JsonConverterAttribute' 型 '{0}' はコンバーター型ではないか、アクセス可能なパラメーターなしのコンストラクターを含んでいません。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf index 66cf520a91301..3e4f9eb8eadd7 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ko.xlf @@ -62,6 +62,16 @@ 초기화 전용 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. '{1}' 멤버에 지정된 'JsonConverterAttribute' 형식 '{0}'이(가) 변환기 형식이 아니거나 액세스 가능한 매개 변수가 없는 생성자를 포함하지 않습니다. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf index b45ee908deaee..8474b4bb21f6b 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pl.xlf @@ -62,6 +62,16 @@ Deserializacja właściwości tylko do inicjowania nie jest obecnie obsługiwana w trybie generowania źródła. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. Typ „{0}” „JsonConverterAttribute” określony w przypadku składowej „{1}” nie jest typem konwertera lub nie zawiera dostępnego konstruktora bez parametrów. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf index 0e091897fa10d..f8c41b215fbfa 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.pt-BR.xlf @@ -62,6 +62,16 @@ A desserialização de propriedades apenas de inicialização não é atualmente suportada no modo de geração de origem. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. O tipo "JsonConverterAttribute" "{0}" especificado no membro "{1}" não é um tipo de conversor ou não contém um construtor sem parâmetros acessível. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf index 4538b39596648..39cbe37e5514c 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.ru.xlf @@ -62,6 +62,16 @@ Десериализация свойств, предназначенных только для инициализации, сейчас не поддерживается в режиме создания исходного кода. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. Тип "JsonConverterAttribute" "{0}", указанный в элементе "{1}", не является типом преобразователя или не содержит доступного конструктора без параметров. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf index 808d8756fa9c0..f89d0b052db8f 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.tr.xlf @@ -62,6 +62,16 @@ Yalnızca başlangıç özelliklerini seri durumdan çıkarma şu anda kaynak oluşturma modunda desteklenmiyor. + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. '{1}' üyesi üzerinde belirtilen 'JsonConverterAttribute' '{0}' türü dönüştürücü türü değil veya erişilebilir parametresiz bir oluşturucu içermiyor. diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf index 427d532a15285..dcefcc43c2b04 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -62,6 +62,16 @@ 源生成模式当前不支持仅初始化属性的反序列化。 + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. 在成员 "{1}" 上指定的 "JsonConverterAttribute" 类型 "{0}" 不是转换器类型或不包含可访问的无参数构造函数。 diff --git a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf index 5537bcc18a3e4..0a2d4af6e07ae 100644 --- a/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Text.Json/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -62,6 +62,16 @@ 來源產生模式目前不支援 init-only 屬性的還原序列化。 + + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. + + + + Constructor annotated with JsonConstructorAttribute is inaccessible. + Constructor annotated with JsonConstructorAttribute is inaccessible. + + The 'JsonConverterAttribute' type '{0}' specified on member '{1}' is not a converter type or does not contain an accessible parameterless constructor. 成員 '{1}' 上指定的 'JsonConverterAttribute' 類型 '{0}' 不是轉換器類型,或不包含可存取的無參數建構函式。 diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs index 106c07b1e7a12..6e05e5c8057ac 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionEmitMemberAccessor.cs @@ -112,7 +112,7 @@ public override JsonTypeInfo.ParameterizedConstructorDelegate() + NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", type)); + Assert.Contains("JsonConstructorAttribute", ex.ToString()); + } + + [Theory] + [InlineData(typeof(PrivateParameterizedCtor_WithAttribute), false)] + [InlineData(typeof(InternalParameterizedCtor_WithAttribute), true)] + [InlineData(typeof(ProtectedParameterizedCtor_WithAttribute), false)] + public async Task NonPublicCtors_WithJsonConstructorAttribute_WorksAsExpected(Type type, bool isAccessibleBySourceGen) + { + if (!Serializer.IsSourceGeneratedSerializer || isAccessibleBySourceGen) + { + object? result = await Serializer.DeserializeWrapper("{}", type); + Assert.IsType(type, result); + } + else { - NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}")); + NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", type)); Assert.Contains("JsonConstructorAttribute", ex.ToString()); } - - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); - await RunTestAsync(); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index 94a213ddffe6e..8f891d378c21d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -10,7 +10,8 @@ - $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1034;SYSLIB1037;SYSLIB1038;SYSLIB1039;SYSLIB1220 + + $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1034;SYSLIB1037;SYSLIB1038;SYSLIB1039;SYSLIB1220;SYSLIB1222 true diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index 0d10c217fab26..4fdb962289337 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -480,6 +480,8 @@ public class Location internal int internalField = 2; [JsonInclude] private int privateField = 4; + [JsonInclude] + protected int protectedField = 8; [JsonInclude] public int Id { get; private set; } @@ -494,6 +496,8 @@ public class Location [JsonInclude] internal string InternalProperty { get; set; } [JsonInclude] + protected string ProtectedProperty { get; set; } + [JsonInclude] internal string InternalPropertyWithPrivateGetter { private get; set; } [JsonInclude] internal string InternalPropertyWithPrivateSetter { get; private set; } @@ -730,6 +734,50 @@ internal partial class JsonContext : JsonSerializerContext return CreateCompilation(source); } + public static Compilation CreateCompilationWithJsonConstructorAttributeAnnotations() + { + string source = """ + using System.Text.Json.Serialization; + + namespace HelloWorld + { + public class ClassWithPublicCtor + { + [JsonConstructor] + public ClassWithPublicCtor() { } + } + + public class ClassWithInternalCtor + { + [JsonConstructor] + internal ClassWithInternalCtor() { } + } + + public class ClassWithProtectedCtor + { + [JsonConstructor] + protected ClassWithProtectedCtor() { } + } + + public class ClassWithPrivateCtor + { + [JsonConstructor] + private ClassWithPrivateCtor() { } + } + + [JsonSerializable(typeof(ClassWithPublicCtor))] + [JsonSerializable(typeof(ClassWithInternalCtor))] + [JsonSerializable(typeof(ClassWithProtectedCtor))] + [JsonSerializable(typeof(ClassWithPrivateCtor))] + public partial class MyJsonContext : JsonSerializerContext + { + } + } + """; + + return CreateCompilation(source); + } + internal static void AssertEqualDiagnosticMessages( IEnumerable expectedDiags, IEnumerable actualDiags) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs index d40df53bc3e0d..debc403030af3 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -237,7 +237,7 @@ public void DoNotWarnOnClassesWithConstructorInitOnlyProperties() Compilation compilation = CompilationHelper.CreateCompilationWithConstructorInitOnlyProperties(); CompilationHelper.RunJsonSourceGenerator(compilation); } - + [Fact] public void DoNotWarnOnClassesWithMixedInitOnlyProperties() { @@ -271,6 +271,8 @@ public void WarnOnClassesWithInaccessibleJsonIncludeProperties() Location address2Location = compilation.GetSymbolsWithName("Address2").First().Locations[0]; Location countryLocation = compilation.GetSymbolsWithName("Country").First().Locations[0]; Location privateFieldLocation = compilation.GetSymbolsWithName("privateField").First().Locations[0]; + Location protectedFieldLocation = compilation.GetSymbolsWithName("protectedField").First().Locations[0]; + Location protectedPropertyLocation = compilation.GetSymbolsWithName("ProtectedProperty").First().Locations[0]; Location internalPropertyWithPrivateGetterLocation = compilation.GetSymbolsWithName("InternalPropertyWithPrivateGetter").First().Locations[0]; Location internalPropertyWithPrivateSetterLocation = compilation.GetSymbolsWithName("InternalPropertyWithPrivateSetter").First().Locations[0]; @@ -280,6 +282,8 @@ public void WarnOnClassesWithInaccessibleJsonIncludeProperties() new(DiagnosticSeverity.Warning, address2Location, "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, privateFieldLocation, "The member 'Location.privateField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, protectedFieldLocation, "The member 'Location.protectedField' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + new(DiagnosticSeverity.Warning, protectedPropertyLocation, "The member 'Location.ProtectedProperty' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, internalPropertyWithPrivateGetterLocation, "The member 'Location.InternalPropertyWithPrivateGetter' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), new(DiagnosticSeverity.Warning, internalPropertyWithPrivateSetterLocation, "The member 'Location.InternalPropertyWithPrivateSetter' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), }; @@ -462,5 +466,24 @@ public partial class MyJsonContext : JsonSerializerContext CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); } + + [Fact] + public void TypesWithJsonConstructorAnnotations_WarnAsExpected() + { + Compilation compilation = CompilationHelper.CreateCompilationWithJsonConstructorAttributeAnnotations(); + + JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, disableDiagnosticValidation: true); + + Location protectedCtorLocation = compilation.GetSymbolsWithName("ClassWithProtectedCtor").First().Locations[0]; + Location privateCtorLocation = compilation.GetSymbolsWithName("ClassWithPrivateCtor").First().Locations[0]; + + var expectedDiagnostics = new DiagnosticData[] + { + new(DiagnosticSeverity.Warning, protectedCtorLocation, "The constructor on type 'HelloWorld.ClassWithProtectedCtor' has been annotated with JsonConstructorAttribute but is not accessible by the source generator."), + new(DiagnosticSeverity.Warning, privateCtorLocation, "The constructor on type 'HelloWorld.ClassWithPrivateCtor' has been annotated with JsonConstructorAttribute but is not accessible by the source generator."), + }; + + CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + } } }