diff --git a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs index e8935677ba99bf..7647412edb5137 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs @@ -249,6 +249,9 @@ public KnownTypeSymbols(Compilation compilation) public INamedTypeSymbol? SetsRequiredMembersAttributeType => GetOrResolveType("System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", ref _SetsRequiredMembersAttributeType); private Option _SetsRequiredMembersAttributeType; + public INamedTypeSymbol? UnsafeAccessorAttributeType => GetOrResolveType("System.Runtime.CompilerServices.UnsafeAccessorAttribute", ref _UnsafeAccessorAttributeType); + private Option _UnsafeAccessorAttributeType; + public INamedTypeSymbol? JsonStringEnumConverterType => GetOrResolveType("System.Text.Json.Serialization.JsonStringEnumConverter", ref _JsonStringEnumConverterType); private Option _JsonStringEnumConverterType; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs index 23c0d6ff737d0d..0a30fa49562f56 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.DiagnosticDescriptors.cs @@ -68,14 +68,6 @@ internal static class DiagnosticDescriptors defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - public static DiagnosticDescriptor InaccessibleJsonIncludePropertiesNotSupported { get; } = DiagnosticDescriptorHelper.Create( - id: "SYSLIB1038", - title: new LocalizableResourceString(nameof(SR.InaccessibleJsonIncludePropertiesNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - messageFormat: new LocalizableResourceString(nameof(SR.InaccessibleJsonIncludePropertiesNotSupportedFormat), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), - category: JsonConstants.SystemTextJsonSourceGenerationName, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - public static DiagnosticDescriptor PolymorphismNotSupported { get; } = DiagnosticDescriptorHelper.Create( id: "SYSLIB1039", title: new LocalizableResourceString(nameof(SR.FastPathPolymorphismNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), @@ -100,14 +92,6 @@ internal static class DiagnosticDescriptors defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - public static DiagnosticDescriptor JsonConstructorInaccessible { get; } = DiagnosticDescriptorHelper.Create( - 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); - public static DiagnosticDescriptor DerivedJsonConverterAttributesNotSupported { get; } = DiagnosticDescriptorHelper.Create( id: "SYSLIB1223", title: new LocalizableResourceString(nameof(SR.DerivedJsonConverterAttributesNotSupportedTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)), diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.ExceptionMessages.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.ExceptionMessages.cs index 99a07d9e40d68d..35f34315bfc3ab 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.ExceptionMessages.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.ExceptionMessages.cs @@ -16,15 +16,9 @@ private sealed partial class Emitter /// private static class ExceptionMessages { - public const string InaccessibleJsonIncludePropertiesNotSupported = - "The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."; - public const string IncompatibleConverterType = "The converter '{0}' is not compatible with the type '{1}'."; - public const string InitOnlyPropertySetterNotSupported = - "Setting init-only properties is not supported in source generation mode."; - public const string InvalidJsonConverterFactoryOutput = "The converter '{0}' cannot return null or a JsonConverterFactory instance."; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index e6ed81f419539b..825e2b84837803 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -37,6 +37,7 @@ private sealed partial class Emitter private const string OptionsLocalVariableName = "options"; private const string ValueVarName = "value"; private const string WriterVarName = "writer"; + private const string ValueTypeSetterDelegateName = "ValueTypeSetter"; private const string PreserveReferenceHandlerPropertyName = "Preserve"; private const string IgnoreCyclesReferenceHandlerPropertyName = "IgnoreCycles"; @@ -49,6 +50,9 @@ private sealed partial class Emitter private const string UnsafeTypeRef = "global::System.Runtime.CompilerServices.Unsafe"; private const string EqualityComparerTypeRef = "global::System.Collections.Generic.EqualityComparer"; private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair"; + private const string UnsafeAccessorAttributeTypeRef = "global::System.Runtime.CompilerServices.UnsafeAccessorAttribute"; + private const string UnsafeAccessorKindTypeRef = "global::System.Runtime.CompilerServices.UnsafeAccessorKind"; + private const string BindingFlagsTypeRef = "global::System.Reflection.BindingFlags"; private const string JsonEncodedTextTypeRef = "global::System.Text.Json.JsonEncodedText"; private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy"; private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer"; @@ -92,6 +96,12 @@ private sealed partial class Emitter /// private bool _emitGetConverterForNullablePropertyMethod; + /// + /// Indicates that a value type property setter uses the reflection fallback, + /// requiring the ValueTypeSetter delegate type to be emitted. + /// + private bool _emitValueTypeSetterDelegate; + /// /// The SourceText emit implementation filled by the individual Roslyn versions. /// @@ -120,7 +130,7 @@ public void Emit(ContextGenerationSpec contextGenerationSpec) string contextName = contextGenerationSpec.ContextType.Name; // Add root context implementation. - AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(contextGenerationSpec, _emitGetConverterForNullablePropertyMethod)); + AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(contextGenerationSpec, _emitGetConverterForNullablePropertyMethod, _emitValueTypeSetterDelegate)); // Add GetJsonTypeInfo override implementation. AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation(contextGenerationSpec)); @@ -129,6 +139,7 @@ public void Emit(ContextGenerationSpec contextGenerationSpec) AddSource($"{contextName}.PropertyNames.g.cs", GetPropertyNameInitialization(contextGenerationSpec)); _emitGetConverterForNullablePropertyMethod = false; + _emitValueTypeSetterDelegate = false; _propertyNames.Clear(); _typeIndex.Clear(); } @@ -590,6 +601,12 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene GenerateCtorParamMetadataInitFunc(writer, ctorParamMetadataInitMethodName, typeMetadata); } + // Generate UnsafeAccessor methods or reflection cache fields for property accessors. + _emitValueTypeSetterDelegate |= GeneratePropertyAccessors(writer, typeMetadata); + + // Generate constructor accessor for inaccessible [JsonConstructor] constructors. + GenerateConstructorAccessor(writer, typeMetadata); + writer.Indentation--; writer.WriteLine('}'); @@ -599,6 +616,7 @@ private SourceText GenerateForObject(ContextGenerationSpec contextSpec, TypeGene private void GeneratePropMetadataInitFunc(SourceWriter writer, string propInitMethodName, TypeGenerationSpec typeGenerationSpec) { ImmutableEquatableArray properties = typeGenerationSpec.PropertyGenSpecs; + HashSet duplicateMemberNames = GetDuplicateMemberNames(properties); writer.WriteLine($"private static {JsonPropertyInfoTypeRef}[] {propInitMethodName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName})"); writer.WriteLine('{'); @@ -624,28 +642,8 @@ property.DefaultIgnoreCondition is JsonIgnoreCondition.Always && string propertyTypeFQN = isIgnoredPropertyOfUnusedType ? "object" : property.PropertyType.FullyQualifiedName; - string getterValue = property switch - { - { DefaultIgnoreCondition: JsonIgnoreCondition.Always } => "null", - { CanUseGetter: true } => $"static obj => (({declaringTypeFQN})obj).{propertyName}", - { CanUseGetter: false, HasJsonInclude: true } - => $"""static _ => throw new {InvalidOperationExceptionTypeRef}("{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.TypeRef.Name, propertyName)}")""", - _ => "null" - }; - - string setterValue = property switch - { - { DefaultIgnoreCondition: JsonIgnoreCondition.Always } => "null", - { CanUseSetter: true, IsInitOnlySetter: true } - => $"""static (obj, value) => throw new {InvalidOperationExceptionTypeRef}("{ExceptionMessages.InitOnlyPropertySetterNotSupported}")""", - { CanUseSetter: true } when typeGenerationSpec.TypeRef.IsValueType - => $"""static (obj, value) => {UnsafeTypeRef}.Unbox<{declaringTypeFQN}>(obj).{propertyName} = value!""", - { CanUseSetter: true } - => $"""static (obj, value) => (({declaringTypeFQN})obj).{propertyName} = value!""", - { CanUseSetter: false, HasJsonInclude: true } - => $"""static (obj, value) => throw new {InvalidOperationExceptionTypeRef}("{string.Format(ExceptionMessages.InaccessibleJsonIncludePropertiesNotSupported, typeGenerationSpec.TypeRef.Name, property.MemberName)}")""", - _ => "null", - }; + string getterValue = GetPropertyGetterValue(property, typeGenerationSpec, propertyName, declaringTypeFQN, i, duplicateMemberNames.Contains(property.MemberName)); + string setterValue = GetPropertySetterValue(property, typeGenerationSpec, propertyName, declaringTypeFQN, i, duplicateMemberNames.Contains(property.MemberName)); string ignoreConditionNamedArg = property.DefaultIgnoreCondition.HasValue ? $"{JsonIgnoreConditionTypeRef}.{property.DefaultIgnoreCondition.Value}" @@ -725,6 +723,416 @@ property.DefaultIgnoreCondition is JsonIgnoreCondition.Always && writer.WriteLine('}'); } + /// + /// Returns true if the property requires an unsafe accessor or reflection fallback + /// for its getter (i.e. it's inaccessible but has [JsonInclude]). + /// + private static bool NeedsAccessorForGetter(PropertyGenerationSpec property) + => !property.CanUseGetter && property.HasJsonInclude && property.DefaultIgnoreCondition is not JsonIgnoreCondition.Always; + + /// + /// Returns true if the property requires an unsafe accessor or reflection fallback + /// for its setter (i.e. init-only properties, or inaccessible with [JsonInclude]). + /// + private static bool NeedsAccessorForSetter(PropertyGenerationSpec property) + { + if (property.DefaultIgnoreCondition is JsonIgnoreCondition.Always) + { + return false; + } + + // All init-only properties need an accessor. + if (property is { CanUseSetter: true, IsInitOnlySetter: true }) + { + return true; + } + + // Inaccessible [JsonInclude] properties need an accessor. + if (!property.CanUseSetter && property.HasJsonInclude) + { + return true; + } + + return false; + } + + private static string GetPropertyGetterValue( + PropertyGenerationSpec property, + TypeGenerationSpec typeGenerationSpec, + string propertyName, + string declaringTypeFQN, + int propertyIndex, + bool needsDisambiguation) + { + if (property.DefaultIgnoreCondition is JsonIgnoreCondition.Always) + { + return "null"; + } + + if (property.CanUseGetter) + { + return $"static obj => (({declaringTypeFQN})obj).{propertyName}"; + } + + if (NeedsAccessorForGetter(property)) + { + string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName; + + if (property.CanUseUnsafeAccessors) + { + // UnsafeAccessor externs for value types take 'ref T'. + string castExpr = typeGenerationSpec.TypeRef.IsValueType + ? $"ref {UnsafeTypeRef}.Unbox<{declaringTypeFQN}>(obj)" + : $"({declaringTypeFQN})obj"; + + string accessorName = property.IsProperty + ? GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation) + : GetAccessorName(typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation); + + return $"static obj => {accessorName}({castExpr})"; + } + + // Reflection fallback wrappers are strongly typed; cast in the delegate. + string getterName = GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation); + + return $"static obj => {getterName}(({declaringTypeFQN})obj)"; + } + + return "null"; + } + + private static string GetPropertySetterValue( + PropertyGenerationSpec property, + TypeGenerationSpec typeGenerationSpec, + string propertyName, + string declaringTypeFQN, + int propertyIndex, + bool needsDisambiguation) + { + if (property.DefaultIgnoreCondition is JsonIgnoreCondition.Always) + { + return "null"; + } + + if (property is { CanUseSetter: true, IsInitOnlySetter: true }) + { + return GetAccessorBasedSetterDelegate(property, typeGenerationSpec, declaringTypeFQN, propertyIndex, needsDisambiguation); + } + + if (property.CanUseSetter) + { + return typeGenerationSpec.TypeRef.IsValueType + ? $"""static (obj, value) => {UnsafeTypeRef}.Unbox<{declaringTypeFQN}>(obj).{propertyName} = value!""" + : $"""static (obj, value) => (({declaringTypeFQN})obj).{propertyName} = value!"""; + } + + if (NeedsAccessorForSetter(property)) + { + return GetAccessorBasedSetterDelegate(property, typeGenerationSpec, declaringTypeFQN, propertyIndex, needsDisambiguation); + } + + return "null"; + } + + /// + /// Generates a setter delegate expression that calls the UnsafeAccessor extern directly + /// or the strongly typed reflection wrapper. + /// + private static string GetAccessorBasedSetterDelegate( + PropertyGenerationSpec property, + TypeGenerationSpec typeGenerationSpec, + string declaringTypeFQN, + int propertyIndex, + bool needsDisambiguation) + { + string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName; + + if (property.CanUseUnsafeAccessors) + { + string castExpr = typeGenerationSpec.TypeRef.IsValueType + ? $"ref {UnsafeTypeRef}.Unbox<{declaringTypeFQN}>(obj)" + : $"({declaringTypeFQN})obj"; + + if (property.IsProperty) + { + string accessorName = GetAccessorName(typeFriendlyName, "set", property.MemberName, propertyIndex, needsDisambiguation); + return $"static (obj, value) => {accessorName}({castExpr}, value!)"; + } + + string fieldName = GetAccessorName(typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation); + return $"static (obj, value) => {fieldName}({castExpr}) = value!"; + } + + // Reflection fallback wrapper is strongly typed; cast in the delegate like UnsafeAccessor. + string setterName = GetAccessorName(typeFriendlyName, "set", property.MemberName, propertyIndex, needsDisambiguation); + string setterCastExpr = typeGenerationSpec.TypeRef.IsValueType + ? $"ref {UnsafeTypeRef}.Unbox<{declaringTypeFQN}>(obj)" + : $"({declaringTypeFQN})obj"; + + return $"static (obj, value) => {setterName}({setterCastExpr}, value!)"; + } + + private static bool GeneratePropertyAccessors(SourceWriter writer, TypeGenerationSpec typeGenerationSpec) + { + ImmutableEquatableArray properties = typeGenerationSpec.PropertyGenSpecs; + HashSet duplicateMemberNames = GetDuplicateMemberNames(properties); + bool needsAccessors = false; + bool needsValueTypeSetterDelegate = false; + + for (int i = 0; i < properties.Count; i++) + { + PropertyGenerationSpec property = properties[i]; + bool needsGetterAccessor = NeedsAccessorForGetter(property); + bool needsSetterAccessor = NeedsAccessorForSetter(property); + + if (!needsGetterAccessor && !needsSetterAccessor) + { + continue; + } + + if (!needsAccessors) + { + writer.WriteLine(); + needsAccessors = true; + } + + string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName; + string declaringTypeFQN = property.DeclaringType.FullyQualifiedName; + string propertyTypeFQN = property.PropertyType.FullyQualifiedName; + bool disambiguate = duplicateMemberNames.Contains(property.MemberName); + + if (property.CanUseUnsafeAccessors) + { + string refPrefix = typeGenerationSpec.TypeRef.IsValueType ? "ref " : ""; + + if (property.IsProperty) + { + if (needsGetterAccessor) + { + string accessorName = GetAccessorName(typeFriendlyName, "get", property.MemberName, i, disambiguate); + writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "get_{property.MemberName}")]"""); + writer.WriteLine($"private static extern {propertyTypeFQN} {accessorName}({refPrefix}{declaringTypeFQN} obj);"); + } + + if (needsSetterAccessor) + { + string accessorName = GetAccessorName(typeFriendlyName, "set", property.MemberName, i, disambiguate); + writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Method, Name = "set_{property.MemberName}")]"""); + writer.WriteLine($"private static extern void {accessorName}({refPrefix}{declaringTypeFQN} obj, {propertyTypeFQN} value);"); + } + } + else + { + // Field: single UnsafeAccessor that returns ref T, used for both get and set. + string fieldAccessorName = GetAccessorName(typeFriendlyName, "field", property.MemberName, i, disambiguate); + writer.WriteLine($"""[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Field, Name = "{property.MemberName}")]"""); + writer.WriteLine($"private static extern ref {propertyTypeFQN} {fieldAccessorName}({refPrefix}{declaringTypeFQN} obj);"); + } + } + else if (property.IsProperty) + { + // Reflection fallback for properties: use Delegate.CreateDelegate on the MethodInfo for efficient invocation. + // Wrapper methods are strongly typed to match UnsafeAccessor signatures. + string propertyExpr = $"typeof({declaringTypeFQN}).GetProperty({FormatStringLiteral(property.MemberName)}, {BindingFlagsTypeRef}.Instance | {BindingFlagsTypeRef}.Public | {BindingFlagsTypeRef}.NonPublic)!"; + + if (needsGetterAccessor) + { + string cacheName = GetReflectionCacheName(typeFriendlyName, "get", property.MemberName, i, disambiguate); + string wrapperName = GetAccessorName(typeFriendlyName, "get", property.MemberName, i, disambiguate); + string delegateType = $"global::System.Func<{declaringTypeFQN}, {propertyTypeFQN}>"; + writer.WriteLine($"private static {delegateType}? {cacheName};"); + writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}({declaringTypeFQN} obj) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetGetMethod(true)!))(obj);"); + } + + if (needsSetterAccessor) + { + string cacheName = GetReflectionCacheName(typeFriendlyName, "set", property.MemberName, i, disambiguate); + string wrapperName = GetAccessorName(typeFriendlyName, "set", property.MemberName, i, disambiguate); + + if (typeGenerationSpec.TypeRef.IsValueType) + { + // For value types, use a ref-parameter delegate to mutate the unboxed value in-place. + needsValueTypeSetterDelegate = true; + string delegateType = $"{ValueTypeSetterDelegateName}<{declaringTypeFQN}, {propertyTypeFQN}>"; + writer.WriteLine($"private static {delegateType}? {cacheName};"); + writer.WriteLine($"private static void {wrapperName}(ref {declaringTypeFQN} obj, {propertyTypeFQN} value) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetSetMethod(true)!))(ref obj, value);"); + } + else + { + string delegateType = $"global::System.Action<{declaringTypeFQN}, {propertyTypeFQN}>"; + writer.WriteLine($"private static {delegateType}? {cacheName};"); + writer.WriteLine($"private static void {wrapperName}({declaringTypeFQN} obj, {propertyTypeFQN} value) => ({cacheName} ??= ({delegateType})global::System.Delegate.CreateDelegate(typeof({delegateType}), {propertyExpr}.GetSetMethod(true)!))(obj, value);"); + } + } + } + else + { + // Reflection fallback for fields: cache the FieldInfo and use GetValue/SetValue. + // Fields don't have MethodInfo, so Delegate.CreateDelegate can't be used. + string fieldExpr = $"typeof({declaringTypeFQN}).GetField({FormatStringLiteral(property.MemberName)}, {BindingFlagsTypeRef}.Instance | {BindingFlagsTypeRef}.Public | {BindingFlagsTypeRef}.NonPublic)!"; + string fieldCacheName = GetReflectionCacheName(typeFriendlyName, "field", property.MemberName, i, disambiguate); + writer.WriteLine($"private static global::System.Reflection.FieldInfo? {fieldCacheName};"); + + if (needsGetterAccessor) + { + string wrapperName = GetAccessorName(typeFriendlyName, "get", property.MemberName, i, disambiguate); + writer.WriteLine($"private static {propertyTypeFQN} {wrapperName}(object obj) => ({propertyTypeFQN})({fieldCacheName} ??= {fieldExpr}).GetValue(obj)!;"); + } + + if (needsSetterAccessor) + { + string wrapperName = GetAccessorName(typeFriendlyName, "set", property.MemberName, i, disambiguate); + writer.WriteLine($"private static void {wrapperName}(object obj, {propertyTypeFQN} value) => ({fieldCacheName} ??= {fieldExpr}).SetValue(obj, value);"); + } + } + } + + return needsValueTypeSetterDelegate; + } + + /// + /// Gets the accessor name for a property or field. For UnsafeAccessor this is the extern method name; + /// for reflection fallback this is the strongly typed wrapper method name. + /// Use kind "get"/"set" for property getters/setters, or "field" for field UnsafeAccessor externs. + /// The property index suffix is only appended when needed to disambiguate shadowed members. + /// + private static string GetAccessorName(string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation) + => needsDisambiguation + ? $"__{accessorKind}_{typeFriendlyName}_{memberName}_{propertyIndex}" + : $"__{accessorKind}_{typeFriendlyName}_{memberName}"; + + private static string GetReflectionCacheName(string typeFriendlyName, string accessorKind, string memberName, int propertyIndex, bool needsDisambiguation) + => needsDisambiguation + ? $"s_{accessorKind}_{typeFriendlyName}_{memberName}_{propertyIndex}" + : $"s_{accessorKind}_{typeFriendlyName}_{memberName}"; + + /// + /// Returns the set of member names that appear more than once in the property list. + /// This occurs when derived types shadow base members via the new keyword. + /// + private static HashSet GetDuplicateMemberNames(ImmutableEquatableArray properties) + { + HashSet seen = new(); + HashSet duplicates = new(); + foreach (PropertyGenerationSpec property in properties) + { + if (!seen.Add(property.MemberName)) + { + duplicates.Add(property.MemberName); + } + } + + return duplicates; + } + + /// + /// Gets the unified constructor accessor name. The wrapper has the same + /// signature for both UnsafeAccessor and reflection fallback: + /// static TypeName __ctor_TypeName(params) + /// + private static string GetConstructorAccessorName(TypeGenerationSpec typeSpec) + => $"__ctor_{typeSpec.TypeInfoPropertyName}"; + + private static string GetConstructorReflectionCacheName(TypeGenerationSpec typeSpec) + => $"s_ctor_{typeSpec.TypeInfoPropertyName}"; + + /// + /// Generates the constructor accessor for inaccessible constructors. + /// For UnsafeAccessor: emits a [UnsafeAccessor(Constructor)] extern method. + /// For reflection fallback: emits a cached ConstructorInfo and a wrapper method. + /// + private static void GenerateConstructorAccessor(SourceWriter writer, TypeGenerationSpec typeSpec) + { + if (!typeSpec.ConstructorIsInaccessible) + { + return; + } + + writer.WriteLine(); + + string typeFQN = typeSpec.TypeRef.FullyQualifiedName; + string wrapperName = GetConstructorAccessorName(typeSpec); + ImmutableEquatableArray parameters = typeSpec.CtorParamGenSpecs; + + // Build the parameter list for the wrapper method. + var wrapperParams = new StringBuilder(); + var callArgs = new StringBuilder(); + + foreach (ParameterGenerationSpec param in parameters) + { + if (wrapperParams.Length > 0) + { + wrapperParams.Append(", "); + callArgs.Append(", "); + } + + wrapperParams.Append($"{param.ParameterType.FullyQualifiedName} p{param.ParameterIndex}"); + callArgs.Append($"p{param.ParameterIndex}"); + } + + if (typeSpec.CanUseUnsafeAccessorForConstructor) + { + writer.WriteLine($"[{UnsafeAccessorAttributeTypeRef}({UnsafeAccessorKindTypeRef}.Constructor)]"); + writer.WriteLine($"private static extern {typeFQN} {wrapperName}({wrapperParams});"); + } + else + { + // Reflection fallback: cached ConstructorInfo + Invoke. + // Note: ConstructorInfo cannot be wrapped in a delegate, so we cache the ConstructorInfo directly. + string cacheName = GetConstructorReflectionCacheName(typeSpec); + + string argTypes = parameters.Count == 0 + ? EmptyTypeArray + : $"new global::System.Type[] {{{string.Join(", ", parameters.Select(p => $"typeof({p.ParameterType.FullyQualifiedName})"))}}}"; + + writer.WriteLine($"private static global::System.Reflection.ConstructorInfo? {cacheName};"); + + string invokeArgs = parameters.Count == 0 + ? "null" + : $"new object?[] {{{string.Join(", ", parameters.Select(p => $"p{p.ParameterIndex}"))}}}"; + + writer.WriteLine($"private static {typeFQN} {wrapperName}({wrapperParams}) => ({typeFQN})({cacheName} ??= typeof({typeFQN}).GetConstructor({InstanceMemberBindingFlagsVariableName}, binder: null, {argTypes}, modifiers: null)!).Invoke({invokeArgs});"); + } + } + + /// + /// Returns the expression for reading a property value in the fast-path serialization handler. + /// For accessible properties, this is a direct member access. For inaccessible [JsonInclude] + /// properties, this uses UnsafeAccessor or reflection. + /// + private static string GetFastPathPropertyValueExpr( + PropertyGenerationSpec property, + TypeGenerationSpec typeGenSpec, + string objectExpr, + int propertyIndex, + bool needsDisambiguation) + { + if (property.CanUseGetter) + { + return $"{objectExpr}.{property.NameSpecifiedInSourceCode}"; + } + + // Inaccessible [JsonInclude] property/field: call accessor directly. + string typeFriendlyName = typeGenSpec.TypeInfoPropertyName; + + if (property.CanUseUnsafeAccessors) + { + string accessorName = property.IsProperty + ? GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation) + : GetAccessorName(typeFriendlyName, "field", property.MemberName, propertyIndex, needsDisambiguation); + + // Value type externs take 'ref T'; use the raw parameter variable to avoid + // ref-of-cast issues. Reference type externs take the declaring type by value. + return typeGenSpec.TypeRef.IsValueType + ? $"{accessorName}(ref {ValueVarName})" + : $"{accessorName}({objectExpr})"; + } + + string getterName = GetAccessorName(typeFriendlyName, "get", property.MemberName, propertyIndex, needsDisambiguation); + + return $"{getterName}({objectExpr})"; + } + private static void GenerateCtorParamMetadataInitFunc(SourceWriter writer, string ctorParamMetadataInitMethodName, TypeGenerationSpec typeGenerationSpec) { ImmutableEquatableArray parameters = typeGenerationSpec.CtorParamGenSpecs; @@ -801,6 +1209,8 @@ private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGeneratio GenerateFastPathFuncHeader(writer, typeGenSpec, serializeMethodName); + HashSet duplicateMemberNames = GetDuplicateMemberNames(typeGenSpec.PropertyGenSpecs); + if (typeGenSpec.ImplementsIJsonOnSerializing) { writer.WriteLine($"((global::{JsonConstants.IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();"); @@ -845,16 +1255,19 @@ private void GenerateFastPathFuncForObject(SourceWriter writer, ContextGeneratio : ValueVarName; string propValueExpr; + // For inaccessible [JsonInclude] properties, use UnsafeAccessor or reflection. + string? rawValueExpr = GetFastPathPropertyValueExpr(propertyGenSpec, typeGenSpec, objectExpr, i, duplicateMemberNames.Contains(propertyGenSpec.MemberName)); + if (defaultCheckType != SerializedValueCheckType.None) { // Use temporary variable to evaluate property value only once string localVariableName = $"__value_{propertyGenSpec.NameSpecifiedInSourceCode.TrimStart('@')}"; - writer.WriteLine($"{propertyGenSpec.PropertyType.FullyQualifiedName} {localVariableName} = {objectExpr}.{propertyGenSpec.NameSpecifiedInSourceCode};"); + writer.WriteLine($"{propertyGenSpec.PropertyType.FullyQualifiedName} {localVariableName} = {rawValueExpr};"); propValueExpr = localVariableName; } else { - propValueExpr = $"{objectExpr}.{propertyGenSpec.NameSpecifiedInSourceCode}"; + propValueExpr = rawValueExpr; } switch (defaultCheckType) @@ -935,7 +1348,28 @@ private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec type const string ArgsVarName = "args"; - StringBuilder sb = new($"static {ArgsVarName} => new {typeGenerationSpec.TypeRef.FullyQualifiedName}("); + // Determine if any non-matching member initializers exist for an inaccessible constructor. + // These need post-construction setter calls since object initializer syntax can't be used. + bool needsPostCtorInitializers = typeGenerationSpec.ConstructorIsInaccessible + && propertyInitializers.Any(p => !p.MatchesConstructorParameter); + + if (needsPostCtorInitializers) + { + return GetParameterizedCtorWithPostInitFunc(typeGenerationSpec, parameters, propertyInitializers); + } + + StringBuilder sb; + + if (typeGenerationSpec.ConstructorIsInaccessible) + { + // Inaccessible constructor: use the unified constructor accessor wrapper. + string accessorName = GetConstructorAccessorName(typeGenerationSpec); + sb = new($"static {ArgsVarName} => {accessorName}("); + } + else + { + sb = new($"static {ArgsVarName} => new {typeGenerationSpec.TypeRef.FullyQualifiedName}("); + } if (parameters.Count > 0) { @@ -952,6 +1386,7 @@ private static string GetParameterizedCtorInvocationFunc(TypeGenerationSpec type if (propertyInitializers.Count > 0) { + Debug.Assert(!typeGenerationSpec.ConstructorIsInaccessible); sb.Append("{ "); foreach (PropertyInitializerGenerationSpec property in propertyInitializers) { @@ -968,6 +1403,101 @@ static string GetParamUnboxing(TypeRef type, int index) => $"({type.FullyQualifiedName}){ArgsVarName}[{index}]"; } + /// + /// Generates a statement-body lambda for inaccessible constructors that also have + /// required property member initializers. Since object initializer syntax can't be used + /// with accessor-invoked constructors, the required properties are set individually + /// after construction using property setters or accessor methods. + /// + private static string GetParameterizedCtorWithPostInitFunc( + TypeGenerationSpec typeGenerationSpec, + ImmutableEquatableArray parameters, + ImmutableEquatableArray propertyInitializers) + { + const string ArgsVarName = "args"; + const string ObjVarName = "obj"; + string accessorName = GetConstructorAccessorName(typeGenerationSpec); + HashSet duplicateMemberNames = GetDuplicateMemberNames(typeGenerationSpec.PropertyGenSpecs); + + StringBuilder sb = new(); + sb.AppendLine($"static {ArgsVarName} =>"); + sb.AppendLine("{"); + + // Construct the object via accessor + sb.Append($" var {ObjVarName} = {accessorName}("); + if (parameters.Count > 0) + { + foreach (ParameterGenerationSpec param in parameters) + { + sb.Append($"({param.ParameterType.FullyQualifiedName}){ArgsVarName}[{param.ParameterIndex}], "); + } + + sb.Length -= 2; + } + sb.AppendLine(");"); + + // Set member initializer properties post-construction + string typeFriendlyName = typeGenerationSpec.TypeInfoPropertyName; + foreach (PropertyInitializerGenerationSpec propInit in propertyInitializers) + { + if (propInit.MatchesConstructorParameter) + { + continue; + } + + string value = $"({propInit.ParameterType.FullyQualifiedName}){ArgsVarName}[{propInit.ParameterIndex}]"; + + // Find the matching PropertyGenerationSpec to determine how to set it + PropertyGenerationSpec? matchingProp = null; + int matchingIndex = 0; + for (int i = 0; i < typeGenerationSpec.PropertyGenSpecs.Count; i++) + { + if (typeGenerationSpec.PropertyGenSpecs[i].NameSpecifiedInSourceCode == propInit.Name) + { + matchingProp = typeGenerationSpec.PropertyGenSpecs[i]; + matchingIndex = i; + break; + } + } + + if (matchingProp is not null && (matchingProp.IsInitOnlySetter || NeedsAccessorForSetter(matchingProp))) + { + // Use the accessor method for init-only or inaccessible setters. + bool disambiguate = duplicateMemberNames.Contains(matchingProp.MemberName); + string refPrefix = typeGenerationSpec.TypeRef.IsValueType ? "ref " : ""; + + if (matchingProp.IsProperty) + { + string setterName = GetAccessorName(typeFriendlyName, "set", matchingProp.MemberName, matchingIndex, disambiguate); + sb.AppendLine($" {setterName}({refPrefix}{ObjVarName}, {value});"); + } + else if (matchingProp.CanUseUnsafeAccessors) + { + // UnsafeAccessor field returns ref T: assignment syntax. + string fieldName = GetAccessorName(typeFriendlyName, "field", matchingProp.MemberName, matchingIndex, disambiguate); + sb.AppendLine($" {fieldName}({refPrefix}{ObjVarName}) = {value};"); + } + else + { + // Reflection fallback field setter uses "set" kind and takes (object, T). + string setterName = GetAccessorName(typeFriendlyName, "set", matchingProp.MemberName, matchingIndex, disambiguate); + sb.AppendLine($" {setterName}({ObjVarName}, {value});"); + } + } + else + { + // Direct property assignment for accessible setters + sb.AppendLine($" {ObjVarName}.{propInit.Name} = {value};"); + } + } + + sb.Append($" return {ObjVarName};"); + sb.AppendLine(); + sb.Append('}'); + + return sb.ToString(); + } + private static string? GetPrimitiveWriterMethod(TypeGenerationSpec type) { return type.PrimitiveTypeKind switch @@ -1126,7 +1656,7 @@ private static void GenerateTypeInfoFactoryFooter(SourceWriter writer) """); } - private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec contextSpec, bool emitGetConverterForNullablePropertyMethod) + private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec contextSpec, bool emitGetConverterForNullablePropertyMethod, bool emitValueTypeSetterDelegate) { string contextTypeRef = contextSpec.ContextType.FullyQualifiedName; string contextTypeName = contextSpec.ContextType.Name; @@ -1150,6 +1680,12 @@ private static SourceText GetRootJsonContextImplementation(ContextGenerationSpec """); + if (emitValueTypeSetterDelegate) + { + writer.WriteLine($"private delegate void {ValueTypeSetterDelegateName}(ref TDeclaringType obj, TValue value);"); + writer.WriteLine(); + } + writer.WriteLine($$""" /// /// The default associated with a default instance. @@ -1493,7 +2029,9 @@ private static string FormatDefaultConstructorExpr(TypeGenerationSpec typeSpec) { { RuntimeTypeRef: TypeRef runtimeType } => $"() => new {runtimeType.FullyQualifiedName}()", { IsValueTuple: true } => $"() => default({typeSpec.TypeRef.FullyQualifiedName})", - { ConstructionStrategy: ObjectConstructionStrategy.ParameterlessConstructor } => $"() => new {typeSpec.TypeRef.FullyQualifiedName}()", + { ConstructionStrategy: ObjectConstructionStrategy.ParameterlessConstructor, ConstructorIsInaccessible: false } => $"() => new {typeSpec.TypeRef.FullyQualifiedName}()", + { ConstructionStrategy: ObjectConstructionStrategy.ParameterlessConstructor, ConstructorIsInaccessible: true } => + $"static () => {GetConstructorAccessorName(typeSpec)}()", _ => "null", }; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index e70fd9f01ef341..c184c7f5674eaa 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -576,6 +576,7 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener List? fastPathPropertyIndices = null; ObjectConstructionStrategy constructionStrategy = default; bool constructorSetsRequiredMembers = false; + bool constructorIsInaccessible = false; ParameterGenerationSpec[]? ctorParamSpecs = null; List? propertyInitializerSpecs = null; CollectionType collectionType = CollectionType.NotApplicable; @@ -681,11 +682,8 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener { ReportDiagnostic(DiagnosticDescriptors.MultipleJsonConstructorAttribute, typeToGenerate.Location, type.ToDisplayString()); } - else if (constructor != null && !IsSymbolAccessibleWithin(constructor, within: contextType)) - { - ReportDiagnostic(DiagnosticDescriptors.JsonConstructorInaccessible, typeToGenerate.Location, type.ToDisplayString()); - constructor = null; - } + + constructorIsInaccessible = constructor is not null && !IsSymbolAccessibleWithin(constructor, within: contextType); classType = ClassType.Object; @@ -733,6 +731,10 @@ private TypeGenerationSpec ParseTypeGenerationSpec(in TypeToGenerate typeToGener CollectionValueType = collectionValueType, ConstructionStrategy = constructionStrategy, ConstructorSetsRequiredParameters = constructorSetsRequiredMembers, + ConstructorIsInaccessible = constructorIsInaccessible, + CanUseUnsafeAccessorForConstructor = constructorIsInaccessible + && _knownSymbols.UnsafeAccessorAttributeType is not null + && type is not INamedTypeSymbol { IsGenericType: true }, NullableUnderlyingType = nullableUnderlyingType, RuntimeTypeRef = runtimeTypeRef, IsValueTuple = type.IsTupleType, @@ -1120,13 +1122,9 @@ void AddMember( AddPropertyWithConflictResolution(propertySpec, memberInfo, propertyIndex: properties.Count, ref state); properties.Add(propertySpec); - // In case of JsonInclude fail if either: - // 1. the getter is not accessible by the source generator or - // 2. neither getter or setter methods are public. - if (propertySpec.HasJsonInclude && (!propertySpec.CanUseGetter || !propertySpec.IsPublic)) - { - state.HasInvalidConfigurationForFastPath = true; - } + // Inaccessible [JsonInclude] properties are now supported via + // UnsafeAccessor (when available) or reflection fallback. + // No longer mark the type as invalid for fast-path. } bool PropertyIsOverriddenAndIgnored(IPropertySymbol property, Dictionary? ignoredMembers) @@ -1304,7 +1302,8 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type) if (hasJsonIncludeButIsInaccessible) { - ReportDiagnostic(DiagnosticDescriptors.InaccessibleJsonIncludePropertiesNotSupported, memberInfo.GetLocation(), declaringType.Name, memberInfo.Name); + // Inaccessible [JsonInclude] properties are now supported via + // UnsafeAccessor (when available) or reflection fallback. } if (isExtensionData) @@ -1380,6 +1379,8 @@ private bool IsValidDataExtensionPropertyType(ITypeSymbol type) ObjectCreationHandling = objectCreationHandling, Order = order, HasJsonInclude = hasJsonInclude, + CanUseUnsafeAccessors = _knownSymbols.UnsafeAccessorAttributeType is not null + && memberInfo.ContainingType is not INamedTypeSymbol { IsGenericType: true }, IsExtensionData = isExtensionData, PropertyType = propertyTypeRef, DeclaringType = declaringType, @@ -1682,11 +1683,13 @@ private void ProcessMember( return null; } - HashSet? memberInitializerNames = null; + HashSet? requiredMemberNames = null; List? propertyInitializers = null; int paramCount = constructorParameters?.Length ?? 0; - // Determine potential init-only or required properties that need to be part of the constructor delegate signature. + // Determine required properties that need to be part of the constructor delegate signature. + // Init-only non-required properties are no longer included here -- they will be set + // via UnsafeAccessor or reflection post-construction to preserve their default values. foreach (PropertyGenerationSpec property in properties) { if (!property.CanUseSetter) @@ -1699,11 +1702,11 @@ private void ProcessMember( continue; } - if ((property.IsRequired && !constructorSetsRequiredMembers) || property.IsInitOnlySetter) + if (property.IsRequired && !constructorSetsRequiredMembers) { - if (!(memberInitializerNames ??= new()).Add(property.MemberName)) + if (!(requiredMemberNames ??= new()).Add(property.MemberName)) { - // We've already added another member initializer with the same name to our spec list. + // We've already added another required member with the same name to our spec list. // Duplicates can occur here because the provided list of properties includes shadowed members. // This is because we generate metadata for *all* members, including shadowed or ignored ones, // since we need to re-run the deduplication algorithm taking run-time configuration into account. diff --git a/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs index a003a9d74d48c2..621efdd15a421f 100644 --- a/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/PropertyGenerationSpec.cs @@ -132,6 +132,13 @@ public sealed record PropertyGenerationSpec /// public required bool HasJsonInclude { get; init; } + /// + /// Whether the property can use UnsafeAccessor for its getter/setter. + /// This is false when the declaring type is generic or when UnsafeAccessorAttribute + /// is not available in the target compilation. + /// + public required bool CanUseUnsafeAccessors { get; init; } + /// /// Whether the property has the JsonExtensionDataAttribute. /// @@ -163,8 +170,9 @@ public bool ShouldIncludePropertyForFastPath(ContextGenerationSpec contextSpec) return false; } - // Discard properties without getters - if (!CanUseGetter) + // Discard properties without getters, unless they have [JsonInclude] + // in which case we use UnsafeAccessor or reflection to read the value. + if (!CanUseGetter && !HasJsonInclude) { return false; } diff --git a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs index 9b71bf16438b89..6b8491e584bbb1 100644 --- a/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/Model/TypeGenerationSpec.cs @@ -83,6 +83,18 @@ public sealed record TypeGenerationSpec public required bool ConstructorSetsRequiredParameters { get; init; } + /// + /// Whether the deserialization constructor is inaccessible from the generated context. + /// When true, UnsafeAccessor or reflection is used to invoke the constructor. + /// + public required bool ConstructorIsInaccessible { get; init; } + + /// + /// Whether UnsafeAccessors can be used for the constructor. + /// False when the declaring type is generic or UnsafeAccessorAttribute is not available. + /// + public required bool CanUseUnsafeAccessorForConstructor { get; init; } + public required TypeRef? NullableUnderlyingType { get; init; } /// diff --git a/src/libraries/System.Text.Json/gen/Resources/Strings.resx b/src/libraries/System.Text.Json/gen/Resources/Strings.resx index d49246d75cec15..028deaf7716046 100644 --- a/src/libraries/System.Text.Json/gen/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/gen/Resources/Strings.resx @@ -153,18 +153,6 @@ Data extension property type invalid. - - Deserialization of init-only properties is currently not supported in source generation mode. - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - 'JsonDerivedTypeAttribute' is not supported in 'JsonSourceGenerationMode.Serialization'. @@ -195,12 +183,6 @@ 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. - Types annotated with JsonSerializableAttribute must be classes deriving from JsonSerializerContext. 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 9f45bf20887b6c..2da5a6fddf9928 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 @@ -52,36 +52,6 @@ Atribut JsonDerivedTypeAttribute se v JsonSourceGenerationMode.Serialization nepodporuje. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - Člen {0}.{1} má anotaci od JsonIncludeAttribute, ale není pro zdrojový generátor viditelný. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Nepřístupné vlastnosti anotované s JsonIncludeAttribute se v režimu generování zdroje nepodporují. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - Typ {0} definuje vlastnosti pouze pro inicializaci, jejichž deserializace se v režimu generování zdroje v současnosti nepodporuje. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - Konstruktor typu {0} byl opatřen poznámkou s atributem JsonConstructorAttribute, ale zdrojový generátor k němu nemá přístup. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - Konstruktor anotovaný atributem JsonConstructorAttribute je nepřístupný. - - 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 3b5fccacf4baf4..de67ebcb44b92e 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 @@ -52,36 +52,6 @@ „JsonDerivedTypeAttribute“ wird in „JsonSourceGenerationMode.Serialization“ nicht unterstützt. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - Der Member "{0}. {1}" wurde mit dem JsonIncludeAttribute versehen, ist jedoch für den Quellgenerator nicht sichtbar. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Nicht zugängliche Eigenschaften, die mit dem JsonIncludeAttribute versehen sind, werden im Quellgenerierungsmodus nicht unterstützt. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - Der Typ "{0}" definiert nur init-Eigenschaften, deren Deserialisierung im Quellgenerierungsmodus derzeit nicht unterstützt wird. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - Der Konstruktor für den Typ "{0}" wurde mit dem Kommentar "JsonConstructorAttribute" versehen, aber der Quellgenerator kann nicht darauf zugreifen. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - Auf den Konstruktor mit dem Kommentar "JsonConstructorAttribute" kann nicht zugegriffen werden. - - 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 2235a0e7b5a5e3..4357f587aa465d 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 @@ -52,36 +52,6 @@ \"JsonDerivedTypeAttribute\" no se admite en \"JsonSourceGenerationMode.Serialization\". - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - El miembro '{0}.{1}' se ha anotado con JsonIncludeAttribute, pero no es visible para el generador de origen. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Las propiedades inaccesibles anotadas con JsonIncludeAttribute no se admiten en el modo de generación de origen. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - El tipo '{0}' define propiedades de solo inicialización, pero la deserialización no es compatible actualmente con el modo de generación de origen. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - El constructor del tipo '{0}' se ha anotado con JsonConstructorAttribute, pero el generador de origen no puede acceder a él. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - No se puede acceder al constructor anotado con JsonConstructorAttribute. - - 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 03254e940438ad..a8c82bd08be9a2 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 @@ -52,36 +52,6 @@ « JsonDerivedTypeAttribute » n’est pas pris en charge dans « JsonSourceGenerationMode.Serialization ». - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - Le membre '{0}.{1}' a été annoté avec JsonIncludeAttribute mais n’est pas visible pour le générateur source. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Les propriétés inaccessibles annotées avec JsonIncludeAttribute ne sont pas prises en charge en mode de génération de source. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - Le type' {0}' définit des propriétés init uniquement, dont la désérialisation n'est actuellement pas prise en charge en mode de génération de source. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - Le constructeur sur le type '{0}' a été annoté avec JsonConstructorAttribute mais n'est pas accessible par le générateur source. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - Le constructeur annoté avec JsonConstructorAttribute est 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 54fde919e00e7a..2e506f0b070988 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 @@ -52,36 +52,6 @@ 'JsonDerivedTypeAttribute' non è supportato in 'JsonSourceGenerationMode.Serialization'. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - Il membro ' {0}.{1}' è stato annotato con JsonIncludeAttribute ma non è visibile al generatore di origine. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Le proprietà inaccessibili annotate con JsonIncludeAttribute non sono supportate nella modalità di generazione di origine. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - Il tipo '{0}' definisce le proprietà di sola inizializzazione, la cui deserializzazione al momento non è supportata nella modalità di generazione di origine. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - Il costruttore nel tipo '{0}' è stato annotato con JsonConstructorAttribute ma non è accessibile dal generatore di origine. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - Il costruttore annotato con JsonConstructorAttribute non è accessibile. - - 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 6d415ac6d8751d..3904b0bf4d3b32 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 @@ -52,36 +52,6 @@ 'JsonDerivedTypeAttribute' は 'JsonSourceGenerationMode.Serialization' ではサポートされていません。 - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - メンバー '{0}.{1}' には、JsonIncludeAttribute で注釈が付けられていますが、ソース ジェネレーターには表示されません。 - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - JsonIncludeAttribute で注釈が付けられたアクセスできないプロパティは、ソース生成モードではサポートされていません。 - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - 型 '{0}' は、ソース生成モードでは現在サポートされていない init-only プロパティの逆シリアル化を定義します。 - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 現在、ソース生成モードでは init-only プロパティの逆シリアル化はサポートされていません。 - - - - The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. - 型 '{0}' のコンストラクターには JsonConstructorAttribute で注釈が付けられますが、ソース ジェネレーターからアクセスすることはできません。 - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - JsonConstructorAttribute で注釈が付けられたコンストラクターにアクセスできません。 - - 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 a7f25f5f9c7bfd..30c363a49490cf 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 @@ -52,36 +52,6 @@ 'JsonSourceGenerationMode.Serialization'에서는 'JsonDerivedTypeAttribute'가 지원되지 않습니다. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - 멤버 '{0}.{1}'이(가) JsonIncludeAttribute로 주석 처리되었지만 원본 생성기에는 표시되지 않습니다. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - JsonIncludeAttribute로 주석 처리된 액세스할 수 없는 속성은 원본 생성 모드에서 지원되지 않습니다. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - '{0}' 유형은 초기화 전용 속성을 정의하며, 이 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 초기화 전용 속성의 역직렬화는 현재 원본 생성 모드에서 지원되지 않습니다. - - - - The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. - '{0}' 형식의 생성자에 JsonConstructorAttribute로 주석이 추가되었지만 원본 생성기에서 액세스할 수 없습니다. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - JsonConstructorAttribute로 주석이 추가된 생성자에 액세스할 수 없습니다. - - 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 c42e1e7bb368d3..9adc4f9b914c78 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 @@ -52,36 +52,6 @@ Atrybut „JsonDerivedTypeAttribute” nie jest obsługiwany w elemecie „JsonSourceGenerationMode.Serialization”. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - Składowa "{0}. {1}" jest adnotowana za pomocą atrybutu JsonIncludeAttribute, ale nie jest widoczna dla generatora źródła. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Niedostępne właściwości adnotowane za pomocą atrybutu JsonIncludeAttribute nie są obsługiwane w trybie generowania źródła. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - Typ "{0}" określa właściwości tylko do inicjowania, deserializację, która obecnie nie jest obsługiwana w trybie generowania źródła. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - W przypadku konstruktora w zakresie typu „{0}” dokonano adnotacji przy użyciu atrybutu JsonConstructorAttribute, ale nie jest on dostępny dla generatora źródła. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - Konstruktor, w przypadku którego dokonano adnotacji za pomocą atrybutu JsonConstructorAttribute, jest niedostępny. - - 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 d11e95fcc5c7ea..944623252aa289 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 @@ -52,36 +52,6 @@ 'JsonDerivedTypeAttribute' não tem suporte em 'JsonSourceGenerationMode.Serialization'. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - O membro '{0}.{1}' foi anotado com o JsonIncludeAttribute, mas não é visível para o gerador de origem. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Propriedades inacessíveis anotadas com JsonIncludeAttribute não são suportadas no modo de geração de origem. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - O tipo '{0}' define propriedades apenas de inicialização, a desserialização das quais atualmente não é suportada no modo de geração de origem. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - O construtor do tipo '{0}' foi anotado com JsonConstructorAttribute, mas não pode ser acessado pelo gerador de origem. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - O construtor anotado com JsonConstructorAttribute está inacessível. - - 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 88106251593ca4..26036de90c104f 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 @@ -52,36 +52,6 @@ Атрибут JsonDerivedTypeAttribute не поддерживается в \"JsonSourceGenerationMode.Serialization\". - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - Элемент "{0}.{1}" аннотирован с использованием класса JsonIncludeAttribute, но генератор исходного кода не обнаруживает этот элемент. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - Недоступные свойства, аннотированные с использованием класса JsonIncludeAttribute, не поддерживаются в режиме создания исходного кода. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - Тип "{0}" определяет свойства, предназначенные только для инициализации. Их десериализация сейчас не поддерживается в режиме создания исходного кода. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - Десериализация свойств, предназначенных только для инициализации, сейчас не поддерживается в режиме создания исходного кода. - - - - The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. - Конструктор для типа "{0}" аннотирован с использованием JsonConstructorAttribute, но недоступен для генератора источника. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - Конструктор, аннотированный с использованием JsonConstructorAttribute, недоступен. - - 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 328e1488b8c5a2..32f47b0a452a99 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 @@ -52,36 +52,6 @@ 'JsonSourceGenerationMode.Serialization' içinde 'JsonDerivedTypeAttribute' desteklenmiyor. - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - '{0}.{1}' üyesine JsonIncludeAttribute notu eklendi ancak bu üye kaynak oluşturucu tarafından görülmüyor. - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - JsonIncludeAttribute notu eklenmiş erişilemeyen özellikler kaynak oluşturma modunda desteklenmiyor. - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - ‘{0}’ türü, seri durumdan çıkarılması şu anda kaynak oluşturma modunda desteklenmeyen yalnızca başlangıç özelliklerini tanımlar. - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 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. - '{0}' türündeki oluşturucuya JsonConstructorAttribute ile açıklama eklenmiş ancak kaynak oluşturucu tarafından erişilebilir değil. - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - JsonConstructorAttribute ile açıklama eklenmiş oluşturucuya erişilemiyor. - - 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 f0462986fa9400..9077c49d545439 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 @@ -52,36 +52,6 @@ \"JsonSourceGenerationMode.Serialization\" 中不支持 \"JsonDerivedTypeAttribute\"。 - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - 已使用 JsonIncludeAttribute 注释成员“{0}.{1}”,但对源生成器不可见。 - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - 源生成模式不支持使用 JsonIncludeAttribute 注释的不可访问属性。 - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - 类型“{0}”定义仅初始化属性,源生成模式当前不支持其反序列化。 - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 源生成模式当前不支持仅初始化属性的反序列化。 - - - - The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. - 类型“{0}”上的构造函数已使用 JsonConstructorAttribute 进行批注,但源生成器无法访问该构造函数。 - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - 无法访问使用 JsonConstructorAttribute 批注的构造函数。 - - 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 9f6d7d6178753e..d1f46f15eeea0c 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 @@ -52,36 +52,6 @@ 'JsonSourceGenerationMode.Serialization' 中不支援 'JsonDerivedTypeAttribute'。 - - The member '{0}.{1}' has been annotated with the JsonIncludeAttribute but is not visible to the source generator. - 成員 '{0}.{1}' 已經以 JsonIncludeAttribute 標註,但對來源產生器是不可見的。 - - - - Inaccessible properties annotated with the JsonIncludeAttribute are not supported in source generation mode. - 來源產生模式不支援以 JsonIncludeAttribute 標註的無法存取屬性。 - - - - The type '{0}' defines init-only properties, deserialization of which is currently not supported in source generation mode. - 來源產生模式目前不支援類型 '{0}' 定義之 init-only 屬性的還原序列化。 - - - - Deserialization of init-only properties is currently not supported in source generation mode. - 來源產生模式目前不支援 init-only 屬性的還原序列化。 - - - - The constructor on type '{0}' has been annotated with JsonConstructorAttribute but is not accessible by the source generator. - 類型 '{0}' 上的建構函式已使用 JsonConstructorAttribute 標註,但無法供來源產生器存取。 - - - - Constructor annotated with JsonConstructorAttribute is inaccessible. - 無法存取使用 JsonConstructorAttribute 標註的建構函式。 - - 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/JsonMetadataServices.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Helpers.cs index f1464024ba44ac..6cf827e59ee175 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 @@ -148,11 +148,21 @@ 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_JsonIncludeOnInaccessibleProperty(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType); + if (jsonPropertyInfo.Get is null && jsonPropertyInfo.Set is null) + { + // [JsonInclude] property is inaccessible and the source generator + // did not provide getter/setter delegates (e.g. older generator). + Debug.Assert(jsonPropertyInfo.MemberName != null, "MemberName is not set by source gen"); + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnInaccessibleProperty(jsonPropertyInfo.MemberName, jsonPropertyInfo.DeclaringType); + } + + // Non-public [JsonInclude] property with getter/setter delegates provided + // via UnsafeAccessor or reflection fallback — treat it as a valid property. + } + else + { + continue; } - - continue; } if (jsonPropertyInfo.MemberType == MemberTypes.Field && !jsonPropertyInfo.SrcGen_HasJsonInclude && !typeInfo.Options.IncludeFields) diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs index bfba748a867bf8..1d224af8e492c9 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs @@ -22,39 +22,31 @@ public async Task NonPublicCtors_NotSupported(Type type) } [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) + [InlineData(typeof(PrivateParameterizedCtor_WithAttribute))] + [InlineData(typeof(InternalParameterizedCtor_WithAttribute))] + [InlineData(typeof(ProtectedParameterizedCtor_WithAttribute))] + public async Task NonPublicCtors_WithJsonConstructorAttribute_WorksAsExpected(Type type) { - if (!Serializer.IsSourceGeneratedSerializer || isAccessibleBySourceGen) - { - object? result = await Serializer.DeserializeWrapper("{}", type); - Assert.IsType(type, result); - } - else - { - NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", type)); - Assert.Contains("JsonConstructorAttribute", ex.Message); - } + object? result = await Serializer.DeserializeWrapper("{}", type); + Assert.IsType(type, result); + } + + [Fact] + public virtual async Task NonPublicCtor_WithJsonConstructorAttribute_And_RequiredProperty() + { + var result = await Serializer.DeserializeWrapper("""{"X":42,"Name":"test"}"""); + Assert.Equal(42, result.X); + Assert.Equal("test", result.Name); } [Theory] - [InlineData(typeof(PrivateParameterlessCtor_WithAttribute), false)] - [InlineData(typeof(InternalParameterlessCtor_WithAttribute), true)] - [InlineData(typeof(ProtectedParameterlessCtor_WithAttribute), false)] - public async Task NonPublicParameterlessCtors_WithJsonConstructorAttribute_WorksAsExpected(Type type, bool isAccessibleBySourceGen) + [InlineData(typeof(PrivateParameterlessCtor_WithAttribute))] + [InlineData(typeof(InternalParameterlessCtor_WithAttribute))] + [InlineData(typeof(ProtectedParameterlessCtor_WithAttribute))] + public async Task NonPublicParameterlessCtors_WithJsonConstructorAttribute_WorksAsExpected(Type type) { - if (!Serializer.IsSourceGeneratedSerializer || isAccessibleBySourceGen) - { - object? result = await Serializer.DeserializeWrapper("{}", type); - Assert.IsType(type, result); - } - else - { - NotSupportedException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper("{}", type)); - Assert.Contains("JsonConstructorAttribute", ex.Message); - } + object? result = await Serializer.DeserializeWrapper("{}", type); + Assert.IsType(type, result); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs b/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs index d355651eb5bd9c..4aaf962626c994 100644 --- a/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs +++ b/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs @@ -170,12 +170,10 @@ public void TypeWithConstructor_JsonPropertyInfo_AssociatedParameter_MatchesCtor Assert.Empty(parameters); } - [Theory] - [InlineData(typeof(ClassWithRequiredMember))] - [InlineData(typeof(ClassWithInitOnlyProperty))] - public void TypeWithRequiredOrInitMember_SourceGen_HasAssociatedParameterInfo(Type type) + [Fact] + public void TypeWithRequiredMember_SourceGen_HasAssociatedParameterInfo() { - JsonTypeInfo typeInfo = Serializer.GetTypeInfo(type); + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(ClassWithRequiredMember)); JsonPropertyInfo propertyInfo = typeInfo.Properties.Single(); JsonParameterInfo? jsonParameter = propertyInfo.AssociatedParameter; @@ -184,7 +182,7 @@ public void TypeWithRequiredOrInitMember_SourceGen_HasAssociatedParameterInfo(Ty { Assert.NotNull(jsonParameter); - Assert.Equal(type, jsonParameter.DeclaringType); + Assert.Equal(typeof(ClassWithRequiredMember), jsonParameter.DeclaringType); Assert.Equal(0, jsonParameter.Position); Assert.Equal(propertyInfo.PropertyType, jsonParameter.ParameterType); Assert.Equal(propertyInfo.Name, jsonParameter.Name); @@ -201,6 +199,41 @@ public void TypeWithRequiredOrInitMember_SourceGen_HasAssociatedParameterInfo(Ty } } + [Fact] + public void TypeWithInitOnlyMember_SourceGen_HasNoAssociatedParameterInfo() + { + // Non-required init-only properties are no longer part of the constructor delegate + // in source gen. They are set post-construction via UnsafeAccessor or reflection. + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(ClassWithInitOnlyProperty)); + JsonPropertyInfo propertyInfo = typeInfo.Properties.Single(); + + JsonParameterInfo? jsonParameter = propertyInfo.AssociatedParameter; + Assert.Null(jsonParameter); + } + + [Fact] + public void TypeWithInitOnlyAndRequiredMembers_OnlyRequiredHasAssociatedParameterInfo() + { + JsonTypeInfo typeInfo = Serializer.GetTypeInfo(typeof(ClassWithInitOnlyAndRequiredMembers)); + Assert.Equal(2, typeInfo.Properties.Count); + + JsonPropertyInfo initOnlyProp = typeInfo.Properties.Single(p => p.Name == nameof(ClassWithInitOnlyAndRequiredMembers.InitOnlyValue)); + JsonPropertyInfo requiredProp = typeInfo.Properties.Single(p => p.Name == nameof(ClassWithInitOnlyAndRequiredMembers.RequiredValue)); + + Assert.Null(initOnlyProp.AssociatedParameter); + + if (Serializer.IsSourceGeneratedSerializer) + { + Assert.NotNull(requiredProp.AssociatedParameter); + Assert.True(requiredProp.AssociatedParameter.IsMemberInitializer); + Assert.Equal(typeof(ClassWithInitOnlyAndRequiredMembers), requiredProp.AssociatedParameter.DeclaringType); + } + else + { + Assert.Null(requiredProp.AssociatedParameter); + } + } + [Theory] [InlineData(typeof(ClassWithDefaultCtor))] [InlineData(typeof(StructWithDefaultCtor))] @@ -519,6 +552,12 @@ internal class ClassWithInitOnlyProperty public int Value { get; init; } } + internal class ClassWithInitOnlyAndRequiredMembers + { + public int InitOnlyValue { get; init; } + public required string RequiredValue { get; set; } + } + internal class ClassWithMultipleConstructors { public ClassWithMultipleConstructors() { } diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs index 00a37e260301bd..d3adb66f38f2f8 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs @@ -192,5 +192,51 @@ public RecordWithIgnoredNestedInitOnlyProperty(int foo) public record InnerRecord(int Foo, string Bar); } + + [Theory] + [InlineData(typeof(ClassWithInitOnlyPropertyDefaults))] + [InlineData(typeof(StructWithInitOnlyPropertyDefaults))] + public async Task InitOnlyPropertyDefaultValues_PreservedWhenMissingFromJson(Type type) + { + // When no properties are present in JSON, C# default values should be preserved. + object obj = await Serializer.DeserializeWrapper("{}", type); + Assert.Equal("DefaultName", (string)type.GetProperty("Name").GetValue(obj)); + Assert.Equal(42, (int)type.GetProperty("Number").GetValue(obj)); + } + + [Theory] + [InlineData(typeof(ClassWithInitOnlyPropertyDefaults))] + [InlineData(typeof(StructWithInitOnlyPropertyDefaults))] + public async Task InitOnlyPropertyDefaultValues_OverriddenWhenPresentInJson(Type type) + { + // When properties are present in JSON, specified values should be used. + object obj = await Serializer.DeserializeWrapper("""{"Name":"Override","Number":99}""", type); + Assert.Equal("Override", (string)type.GetProperty("Name").GetValue(obj)); + Assert.Equal(99, (int)type.GetProperty("Number").GetValue(obj)); + } + + [Theory] + [InlineData(typeof(ClassWithInitOnlyPropertyDefaults))] + [InlineData(typeof(StructWithInitOnlyPropertyDefaults))] + public async Task InitOnlyPropertyDefaultValues_PartialOverride(Type type) + { + // When only some properties are present in JSON, the rest should keep defaults. + object obj = await Serializer.DeserializeWrapper("""{"Number":99}""", type); + Assert.Equal("DefaultName", (string)type.GetProperty("Name").GetValue(obj)); + Assert.Equal(99, (int)type.GetProperty("Number").GetValue(obj)); + } + + public class ClassWithInitOnlyPropertyDefaults + { + public string Name { get; init; } = "DefaultName"; + public int Number { get; init; } = 42; + } + + public struct StructWithInitOnlyPropertyDefaults + { + public StructWithInitOnlyPropertyDefaults() { } + public string Name { get; init; } = "DefaultName"; + public int Number { get; init; } = 42; + } } } 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 27c821589c748b..5d67b36f9dd0d5 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.NonPublicAccessors.cs @@ -505,5 +505,229 @@ public class TypeThatShouldNotBeGenerated { private protected object _thisLock = new object(); } + + // ---- Extensive test types for [JsonInclude] with inaccessible members ---- + + public class ClassWithPrivateJsonIncludeProperties_Roundtrip + { + [JsonInclude] + private string Name { get; set; } = "default"; + [JsonInclude] + private int Age { get; set; } + + public static ClassWithPrivateJsonIncludeProperties_Roundtrip Create(string name, int age) + { + var obj = new ClassWithPrivateJsonIncludeProperties_Roundtrip(); + obj.Name = name; + obj.Age = age; + return obj; + } + + // For test validation. + internal string GetName() => Name; + internal int GetAge() => Age; + } + + public class ClassWithProtectedJsonIncludeProperties_Roundtrip + { + [JsonInclude] + protected string Name { get; set; } = "default"; + [JsonInclude] + protected int Age { get; set; } + + public static ClassWithProtectedJsonIncludeProperties_Roundtrip Create(string name, int age) + { + var obj = new ClassWithProtectedJsonIncludeProperties_Roundtrip(); + obj.Name = name; + obj.Age = age; + return obj; + } + + // For test validation. + internal string GetName() => Name; + internal int GetAge() => Age; + } + + public class ClassWithMixedAccessibilityJsonIncludeProperties + { + [JsonInclude] + public int PublicProp { get; set; } + [JsonInclude] + internal int InternalProp { get; set; } + [JsonInclude] + private int PrivateProp { get; set; } + [JsonInclude] + protected int ProtectedProp { get; set; } + + internal int GetPrivateProp() => PrivateProp; + internal int GetProtectedProp() => ProtectedProp; + } + + public class ClassWithJsonIncludePrivateInitOnlyProperties + { + [JsonInclude] + public string Name { get; private init; } = "DefaultName"; + [JsonInclude] + public int Number { get; private init; } = 42; + } + + public class ClassWithJsonIncludePrivateGetterProperties + { + [JsonInclude] + public string Name { private get; set; } = "DefaultName"; + [JsonInclude] + public int Number { private get; set; } = 42; + + internal string GetName() => Name; + internal int GetNumber() => Number; + } + + public struct StructWithJsonIncludePrivateProperties + { + [JsonInclude] + private string Name { get; set; } + [JsonInclude] + private int Number { get; set; } + + public static StructWithJsonIncludePrivateProperties Create(string name, int number) => + new StructWithJsonIncludePrivateProperties { Name = name, Number = number }; + + internal readonly string GetName() => Name; + internal readonly int GetNumber() => Number; + } + + /// + /// Generic type with inaccessible [JsonInclude] properties to exercise reflection fallback + /// (UnsafeAccessor does not support generic types). + /// + public class GenericClassWithPrivateJsonIncludeProperties + { + [JsonInclude] + private T Value { get; set; } + + [JsonInclude] + private string Label { get; set; } = "default"; + + public static GenericClassWithPrivateJsonIncludeProperties Create(T value, string label) + { + var obj = new GenericClassWithPrivateJsonIncludeProperties(); + obj.Value = value; + obj.Label = label; + return obj; + } + + public T GetValue() => Value; + public string GetLabel() => Label; + } + + [Fact] + public virtual async Task JsonInclude_PrivateProperties_CanRoundtrip() + { + var obj = ClassWithPrivateJsonIncludeProperties_Roundtrip.Create("Test", 25); + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(@"""Name"":""Test""", json); + Assert.Contains(@"""Age"":25", json); + + var deserialized = await Serializer.DeserializeWrapper(json); + Assert.Equal("Test", deserialized.GetName()); + Assert.Equal(25, deserialized.GetAge()); + } + + [Fact] + public virtual async Task JsonInclude_ProtectedProperties_CanRoundtrip() + { + var obj = ClassWithProtectedJsonIncludeProperties_Roundtrip.Create("Test", 25); + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(@"""Name"":""Test""", json); + Assert.Contains(@"""Age"":25", json); + + var deserialized = await Serializer.DeserializeWrapper(json); + Assert.Equal("Test", deserialized.GetName()); + Assert.Equal(25, deserialized.GetAge()); + } + + [Fact] + public virtual async Task JsonInclude_MixedAccessibility_AllPropertiesRoundtrip() + { + string json = """{"PublicProp":1,"InternalProp":2,"PrivateProp":3,"ProtectedProp":4}"""; + var deserialized = await Serializer.DeserializeWrapper(json); + Assert.Equal(1, deserialized.PublicProp); + Assert.Equal(2, deserialized.InternalProp); + Assert.Equal(3, deserialized.GetPrivateProp()); + Assert.Equal(4, deserialized.GetProtectedProp()); + + string actualJson = await Serializer.SerializeWrapper(deserialized); + Assert.Contains(@"""PublicProp"":1", actualJson); + Assert.Contains(@"""InternalProp"":2", actualJson); + Assert.Contains(@"""PrivateProp"":3", actualJson); + Assert.Contains(@"""ProtectedProp"":4", actualJson); + } + + [Fact] + public virtual async Task JsonInclude_PrivateInitOnlyProperties_PreservesDefaults() + { + // Deserializing empty JSON should preserve default values. + var deserialized = await Serializer.DeserializeWrapper("{}"); + Assert.Equal("DefaultName", deserialized.Name); + Assert.Equal(42, deserialized.Number); + + // Deserializing with values should override defaults. + deserialized = await Serializer.DeserializeWrapper("""{"Name":"Override","Number":100}"""); + Assert.Equal("Override", deserialized.Name); + Assert.Equal(100, deserialized.Number); + + // Serialization should work. + string json = await Serializer.SerializeWrapper(deserialized); + Assert.Contains(@"""Name"":""Override""", json); + Assert.Contains(@"""Number"":100", json); + } + + [Fact] + public virtual async Task JsonInclude_PrivateGetterProperties_CanSerialize() + { + var obj = new ClassWithJsonIncludePrivateGetterProperties { Name = "Test", Number = 99 }; + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(@"""Name"":""Test""", json); + Assert.Contains(@"""Number"":99", json); + + var deserialized = await Serializer.DeserializeWrapper(json); + Assert.Equal("Test", deserialized.GetName()); + Assert.Equal(99, deserialized.GetNumber()); + } + + [Fact] + public virtual async Task JsonInclude_PrivateProperties_EmptyJson_DeserializesToDefault() + { + var deserialized = await Serializer.DeserializeWrapper("{}"); + Assert.Equal("default", deserialized.GetName()); + Assert.Equal(0, deserialized.GetAge()); + } + + [Fact] + public virtual async Task JsonInclude_StructWithPrivateProperties_CanRoundtrip() + { + var obj = StructWithJsonIncludePrivateProperties.Create("Hello", 42); + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(@"""Name"":""Hello""", json); + Assert.Contains(@"""Number"":42", json); + + var deserialized = await Serializer.DeserializeWrapper(json); + Assert.Equal("Hello", deserialized.GetName()); + Assert.Equal(42, deserialized.GetNumber()); + } + + [Fact] + public virtual async Task JsonInclude_GenericType_PrivateProperties_CanRoundtrip() + { + // Generic types use reflection fallback (UnsafeAccessor doesn't support generics). + var obj = GenericClassWithPrivateJsonIncludeProperties.Create(42, "test"); + string json = await Serializer.SerializeWrapper(obj); + Assert.Contains(@"""Value"":42", json); + Assert.Contains(@"""Label"":""test""", json); + + var deserialized = await Serializer.DeserializeWrapper>(json); + Assert.Equal(42, deserialized.GetValue()); + Assert.Equal("test", deserialized.GetLabel()); + } } } diff --git a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs index f3b5962bbb526f..232106f9d52ee7 100644 --- a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs +++ b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Constructor.cs @@ -35,6 +35,15 @@ public class PrivateParameterizedCtor_WithAttribute private PrivateParameterizedCtor_WithAttribute(int x) => X = x; } + public class PrivateParameterizedCtor_WithAttribute_And_RequiredProperty + { + public int X { get; } + public required string Name { get; set; } + + [JsonConstructor] + private PrivateParameterizedCtor_WithAttribute_And_RequiredProperty(int x) => X = x; + } + public class InternalParameterlessCtor { internal InternalParameterlessCtor() { } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs index 0a4c1a21f19fde..4b9f2db997db28 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs @@ -39,6 +39,7 @@ protected ConstructorTests_Metadata(JsonSerializerWrapper stringWrapper) [JsonSerializable(typeof(InternalParameterizedCtor))] [JsonSerializable(typeof(ProtectedParameterizedCtor))] [JsonSerializable(typeof(PrivateParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(PrivateParameterizedCtor_WithAttribute_And_RequiredProperty))] [JsonSerializable(typeof(InternalParameterizedCtor_WithAttribute))] [JsonSerializable(typeof(ProtectedParameterizedCtor_WithAttribute))] [JsonSerializable(typeof(PrivateParameterlessCtor_WithAttribute))] @@ -193,6 +194,7 @@ public ConstructorTests_Default(JsonSerializerWrapper jsonSerializer) : base(jso [JsonSerializable(typeof(InternalParameterizedCtor))] [JsonSerializable(typeof(ProtectedParameterizedCtor))] [JsonSerializable(typeof(PrivateParameterizedCtor_WithAttribute))] + [JsonSerializable(typeof(PrivateParameterizedCtor_WithAttribute_And_RequiredProperty))] [JsonSerializable(typeof(InternalParameterizedCtor_WithAttribute))] [JsonSerializable(typeof(ProtectedParameterizedCtor_WithAttribute))] [JsonSerializable(typeof(PrivateParameterlessCtor_WithAttribute))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs index 920cc2e1726fe3..e1fff34375dc99 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs @@ -36,6 +36,7 @@ public partial class MetadataTests_SourceGen() : MetadataTests(new StringSeriali [JsonSerializable(typeof(StructWithParameterizedCtor))] [JsonSerializable(typeof(ClassWithRequiredMember))] [JsonSerializable(typeof(ClassWithInitOnlyProperty))] + [JsonSerializable(typeof(ClassWithInitOnlyAndRequiredMembers))] [JsonSerializable(typeof(ClassWithMultipleConstructors))] [JsonSerializable(typeof(DerivedClassWithShadowingProperties))] [JsonSerializable(typeof(IDerivedInterface))] 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 87ef2422a4600c..8e08552a74b2c3 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 @@ -47,19 +47,8 @@ void ValidateInvalidOperationException() [Fact] public override async Task Honor_JsonSerializablePropertyAttribute_OnProperties() { - string json = """ - { - "MyInt":1, - "MyString":"Hello", - "MyFloat":2, - "MyUri":"https://microsoft.com" - } - """; - - await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json)); - - var obj = new MyClass_WithNonPublicAccessors_WithPropertyAttributes(); - await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(obj)); + // With unsafe accessors, inaccessible [JsonInclude] properties are now supported in source gen. + await base.Honor_JsonSerializablePropertyAttribute_OnProperties(); } [Theory] @@ -68,8 +57,7 @@ public override async Task Honor_JsonSerializablePropertyAttribute_OnProperties( [InlineData(typeof(Class_PropertyWith_ProtectedInitOnlySetter_WithAttribute))] public override async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) { - bool isDeserializationSupported = type == typeof(Class_PropertyWith_InternalInitOnlySetter_WithAttribute); - + // With unsafe accessors, all init-only [JsonInclude] properties are now supported in source gen. PropertyInfo property = type.GetProperty("MyInt"); // Init-only properties can be serialized. @@ -77,74 +65,61 @@ public override async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) property.SetValue(obj, 1); Assert.Equal("""{"MyInt":1}""", await Serializer.SerializeWrapper(obj, type)); - // Deserializing JsonInclude is only supported for internal properties - if (isDeserializationSupported) - { - obj = await Serializer.DeserializeWrapper("""{"MyInt":1}""", type); - Assert.Equal(1, (int)type.GetProperty("MyInt").GetValue(obj)); - } - else - { - await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper("""{"MyInt":1}""", type)); - } + // Deserialization is now supported for all access levels. + obj = await Serializer.DeserializeWrapper("""{"MyInt":1}""", type); + Assert.Equal(1, (int)type.GetProperty("MyInt").GetValue(obj)); } [Fact] public override async Task HonorCustomConverter_UsingPrivateSetter() { - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - - string json = """{"MyEnum":"AnotherValue","MyInt":2}"""; - - // Deserialization baseline, without enum converter, we get JsonException. NB order of members in deserialized type is significant for this assertion to succeed. - await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json)); - - // JsonInclude not supported in source gen. - await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json, options)); - - // JsonInclude on private getters not supported. - var obj = new StructWithPropertiesWithConverter(); - await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(obj, options)); + // With unsafe accessors, inaccessible [JsonInclude] properties are now supported in source gen. + await base.HonorCustomConverter_UsingPrivateSetter(); } [Fact] public override async Task Public_And_NonPublicPropertyAccessors_PropertyAttributes() { - string json = """{"W":1,"X":2,"Y":3,"Z":4}"""; - - var obj = await Serializer.DeserializeWrapper(json); - Assert.Equal(1, obj.W); - Assert.Equal(2, obj.X); - Assert.Equal(3, obj.Y); - Assert.Equal(4, obj.GetZ); - - await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(obj)); + // With unsafe accessors, inaccessible [JsonInclude] properties are now supported in source gen. + await base.Public_And_NonPublicPropertyAccessors_PropertyAttributes(); } [Fact] public override async Task HonorJsonPropertyName_PrivateGetter() { - string json = """{"prop1":1}"""; - - var obj = await Serializer.DeserializeWrapper(json); - Assert.Equal(MySmallEnum.AnotherValue, obj.GetProxy()); - - // JsonInclude for private members not supported in source gen - await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(obj)); + // With unsafe accessors, inaccessible [JsonInclude] properties are now supported in source gen. + await base.HonorJsonPropertyName_PrivateGetter(); } [Fact] public override async Task HonorJsonPropertyName_PrivateSetter() { - string json = """{"prop2":2}"""; + // With unsafe accessors, inaccessible [JsonInclude] properties are now supported in source gen. + await base.HonorJsonPropertyName_PrivateSetter(); + } + + [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_JsonInclude_WorksAsExpected(Type type, bool _ = true) + { + // With unsafe accessors, all [JsonInclude] members are now supported in source gen. + string json = """{"MyString":"value"}"""; + MemberInfo memberInfo = type.GetMember("MyString", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)[0]; - // JsonInclude for private members not supported in source gen - await Assert.ThrowsAsync(async () => await Serializer.DeserializeWrapper(json)); + 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"); - var obj = new StructWithPropertiesWithJsonPropertyName_PrivateSetter(); - obj.SetProxy(2); - Assert.Equal(json, await Serializer.SerializeWrapper(obj)); + string actualJson = await Serializer.SerializeWrapper(result, type); + Assert.Equal(json, actualJson); } [Fact] @@ -357,6 +332,15 @@ public override async Task JsonIgnoreCondition_TypeLevel_Always_ThrowsInvalidOpe [JsonSerializable(typeof(ClassWithIgnoredAndPrivateMembers))] [JsonSerializable(typeof(ClassWithInternalJsonIncludeProperties))] [JsonSerializable(typeof(ClassWithIgnoredAndPrivateMembers))] + [JsonSerializable(typeof(ClassWithPrivateJsonIncludeProperties_Roundtrip))] + [JsonSerializable(typeof(ClassWithProtectedJsonIncludeProperties_Roundtrip))] + [JsonSerializable(typeof(ClassWithMixedAccessibilityJsonIncludeProperties))] + [JsonSerializable(typeof(ClassWithJsonIncludePrivateInitOnlyProperties))] + [JsonSerializable(typeof(ClassWithJsonIncludePrivateGetterProperties))] + [JsonSerializable(typeof(StructWithJsonIncludePrivateProperties))] + [JsonSerializable(typeof(GenericClassWithPrivateJsonIncludeProperties))] + [JsonSerializable(typeof(ClassWithInitOnlyPropertyDefaults))] + [JsonSerializable(typeof(StructWithInitOnlyPropertyDefaults))] [JsonSerializable(typeof(ClassUsingIgnoreWhenWritingDefaultAttribute))] [JsonSerializable(typeof(ClassUsingIgnoreNeverAttribute))] [JsonSerializable(typeof(ClassWithIgnoredUnsupportedDictionary))] @@ -642,6 +626,15 @@ partial class DefaultContextWithGlobalIgnoreSetting : JsonSerializerContext; [JsonSerializable(typeof(DictionaryWithPrivateKeyAndValueType))] [JsonSerializable(typeof(ClassWithInternalJsonIncludeProperties))] [JsonSerializable(typeof(ClassWithIgnoredAndPrivateMembers))] + [JsonSerializable(typeof(ClassWithPrivateJsonIncludeProperties_Roundtrip))] + [JsonSerializable(typeof(ClassWithProtectedJsonIncludeProperties_Roundtrip))] + [JsonSerializable(typeof(ClassWithMixedAccessibilityJsonIncludeProperties))] + [JsonSerializable(typeof(ClassWithJsonIncludePrivateInitOnlyProperties))] + [JsonSerializable(typeof(ClassWithJsonIncludePrivateGetterProperties))] + [JsonSerializable(typeof(StructWithJsonIncludePrivateProperties))] + [JsonSerializable(typeof(GenericClassWithPrivateJsonIncludeProperties))] + [JsonSerializable(typeof(ClassWithInitOnlyPropertyDefaults))] + [JsonSerializable(typeof(StructWithInitOnlyPropertyDefaults))] [JsonSerializable(typeof(ClassUsingIgnoreWhenWritingDefaultAttribute))] [JsonSerializable(typeof(ClassUsingIgnoreNeverAttribute))] [JsonSerializable(typeof(ClassWithIgnoredUnsupportedDictionary))] 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 0d0e38bb4804c8..910d065b8f3854 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 @@ -9,15 +9,12 @@ - - - - $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1030;SYSLIB1034;SYSLIB1037;SYSLIB1038;SYSLIB1039;SYSLIB1220;SYSLIB1222;SYSLIB1223;SYSLIB1225;SYSLIB1226 + $(NoWarn);SYSLIB0020;SYSLIB0049;SYSLIB1030;SYSLIB1034;SYSLIB1037;SYSLIB1039;SYSLIB1220;SYSLIB1223;SYSLIB1225;SYSLIB1226 true 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 5bac4fd74bf1ca..9e99127d75947d 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 @@ -264,33 +264,13 @@ public void DoNotWarnOnClassesWithRequiredProperties() #endif [Fact] - public void WarnOnClassesWithInaccessibleJsonIncludeProperties() + public void DoesNotWarnOnClassesWithInaccessibleJsonIncludeProperties() { + // Inaccessible [JsonInclude] members are now supported via UnsafeAccessor or reflection fallback. + // No diagnostics should be emitted. Compilation compilation = CompilationHelper.CreateCompilationWithInaccessibleJsonIncludeProperties(); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, disableDiagnosticValidation: true); - - 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 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]; - - 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, 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."), - }; - - CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + CompilationHelper.AssertEqualDiagnosticMessages([], result.Diagnostics); } [Fact] @@ -543,20 +523,11 @@ public partial class MyJsonContext : JsonSerializerContext [Fact] public void TypesWithJsonConstructorAnnotations_WarnAsExpected() { + // Inaccessible [JsonConstructor] constructors are now supported via UnsafeAccessor or reflection fallback. + // No diagnostics should be emitted. 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); + CompilationHelper.AssertEqualDiagnosticMessages([], result.Diagnostics); } [Fact] @@ -776,28 +747,33 @@ public void JsonIgnoreConditionAlwaysOnTypeWarns() [Fact] public void Diagnostic_HasPragmaSuppressibleLocation() { - // SYSLIB1038: JsonInclude attribute on inaccessible member (Warning, configurable). + // SYSLIB1039: Polymorphism not supported for fast-path serialization (Warning, configurable). string source = """ - #pragma warning disable SYSLIB1038 + #pragma warning disable SYSLIB1039 using System.Text.Json.Serialization; - namespace Test + namespace HelloWorld { - public class MyClass + [JsonSerializable(typeof(MyBaseClass), GenerationMode = JsonSourceGenerationMode.Serialization)] + internal partial class JsonContext : JsonSerializerContext { - [JsonInclude] - private int PrivateField; } - [JsonSerializable(typeof(MyClass))] - public partial class JsonContext : JsonSerializerContext { } + [JsonDerivedType(typeof(MyDerivedClass), "derived")] + public class MyBaseClass + { + } + + public class MyDerivedClass : MyBaseClass + { + } } """; Compilation compilation = CompilationHelper.CreateCompilation(source); JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, disableDiagnosticValidation: true); var effective = CompilationWithAnalyzers.GetEffectiveDiagnostics(result.Diagnostics, compilation); - Diagnostic diagnostic = Assert.Single(effective, d => d.Id == "SYSLIB1038"); + Diagnostic diagnostic = Assert.Single(effective, d => d.Id == "SYSLIB1039"); Assert.True(diagnostic.IsSuppressed); }