diff --git a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs
index 36785e95bd05d..8049845e1a218 100644
--- a/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs
+++ b/src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/Person.cs
@@ -47,12 +47,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte
properties[0] = JsonMetadataServices.CreatePropertyInfo(
options,
isProperty: true,
+ isPublic: true,
+ isVirtual: false,
declaringType: typeof(Person),
propertyTypeInfo: jsonContext.Int32,
converter: null,
getter: static (obj) => { return ((Person)obj).Age; },
setter: static (obj, value) => { ((Person)obj).Age = value; },
ignoreCondition: default,
+ hasJsonInclude: false,
numberHandling: default,
propertyName: nameof(Tests.Person.Age),
jsonPropertyName: null);
@@ -60,12 +63,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte
properties[1] = JsonMetadataServices.CreatePropertyInfo(
options,
isProperty: true,
+ isPublic: true,
+ isVirtual: false,
declaringType: typeof(Person),
propertyTypeInfo: jsonContext.String,
converter: null,
getter: static (obj) => { return ((Person)obj).Name; },
setter: static (obj, value) => { ((Person)obj).Name = value; },
ignoreCondition: default,
+ hasJsonInclude: false,
numberHandling: default,
propertyName: nameof(Tests.Person.Name),
jsonPropertyName: null);
@@ -73,12 +79,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte
properties[2] = JsonMetadataServices.CreatePropertyInfo(
options,
isProperty: true,
+ isPublic: true,
+ isVirtual: false,
declaringType: typeof(Person),
propertyTypeInfo: jsonContext.Person,
converter: null,
getter: static (obj) => { return ((Person)obj).Parent; },
setter: static (obj, value) => { ((Person)obj).Parent = value; },
ignoreCondition: default,
+ hasJsonInclude: false,
numberHandling: default,
propertyName: nameof(Tests.Person.Parent),
jsonPropertyName: null);
@@ -86,12 +95,15 @@ private static JsonPropertyInfo[] PersonPropInitFunc(JsonSerializerContext conte
properties[3] = JsonMetadataServices.CreatePropertyInfo(
options,
isProperty: true,
+ isPublic: true,
+ isVirtual: false,
declaringType: typeof(Person),
propertyTypeInfo: jsonContext.String,
converter: null,
getter: static (obj) => { return ((Person)obj).PlaceOfBirth; },
setter: static (obj, value) => { ((Person)obj).PlaceOfBirth = value; },
ignoreCondition: default,
+ hasJsonInclude: false,
numberHandling: default,
propertyName: nameof(Tests.Person.PlaceOfBirth),
jsonPropertyName: null);
diff --git a/src/libraries/System.Text.Json/Common/JsonHelpers.cs b/src/libraries/System.Text.Json/Common/JsonHelpers.cs
new file mode 100644
index 0000000000000..96a2872621f40
--- /dev/null
+++ b/src/libraries/System.Text.Json/Common/JsonHelpers.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Text.Json
+{
+ internal static partial class JsonHelpers
+ {
+ ///
+ /// Emulates Dictionary.TryAdd on netstandard.
+ ///
+ public static bool TryAdd(this Dictionary dictionary, in TKey key, in TValue value) where TKey : notnull
+ {
+#if NETSTANDARD2_0 || NETFRAMEWORK
+ if (!dictionary.ContainsKey(key))
+ {
+ dictionary[key] = value;
+ return true;
+ }
+
+ return false;
+#else
+ return dictionary.TryAdd(key, value);
+#endif
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs
new file mode 100644
index 0000000000000..5d9ef545b8d44
--- /dev/null
+++ b/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Reflection;
+
+namespace System.Text.Json.Reflection
+{
+ internal static partial class ReflectionExtensions
+ {
+ public static bool IsVirtual(this PropertyInfo? propertyInfo)
+ {
+ Debug.Assert(propertyInfo != null);
+ return propertyInfo != null && (propertyInfo.GetMethod?.IsVirtual == true || propertyInfo.SetMethod?.IsVirtual == true);
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs
index f4b27c2db1187..8020f798086e9 100644
--- a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs
+++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs
@@ -3,7 +3,7 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
-using System.Text.Json.SourceGeneration.Reflection;
+using System.Text.Json.Reflection;
namespace System.Text.Json.SourceGeneration
{
@@ -19,6 +19,8 @@ internal sealed class ContextGenerationSpec
public List RootSerializableTypes { get; } = new();
+ public HashSet? NullableUnderlyingTypes { get; } = new();
+
public List ContextClassDeclarationList { get; init; }
///
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
index 811a4ea8365e6..cbab663971eed 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@@ -3,7 +3,9 @@
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
@@ -35,8 +37,9 @@ private sealed partial class Emitter
private const string ArrayTypeRef = "global::System.Array";
private const string InvalidOperationExceptionTypeRef = "global::System.InvalidOperationException";
private const string TypeTypeRef = "global::System.Type";
- private const string UnsafeTypeRef = "global::System.CompilerServices.Unsafe";
+ private const string UnsafeTypeRef = "global::System.Runtime.CompilerServices.Unsafe";
private const string NullableTypeRef = "global::System.Nullable";
+ private const string EqualityComparerTypeRef = "global::System.Collections.Generic.EqualityComparer";
private const string IListTypeRef = "global::System.Collections.Generic.IList";
private const string KeyValuePairTypeRef = "global::System.Collections.Generic.KeyValuePair";
private const string ListTypeRef = "global::System.Collections.Generic.List";
@@ -210,9 +213,9 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec)
{
source = GenerateForObject(typeGenerationSpec);
- if (typeGenerationSpec.PropertiesMetadata != null)
+ if (typeGenerationSpec.PropertyGenSpecList != null)
{
- foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertiesMetadata)
+ foreach (PropertyGenerationSpec metadata in typeGenerationSpec.PropertyGenSpecList)
{
GenerateTypeInfo(metadata.TypeGenerationSpec);
}
@@ -279,7 +282,7 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada
}}
// Allow nullable handling to forward to the underlying type's converter.
- converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(({JsonConverterTypeRef}<{typeCompilableName}>)actualConverter);
+ converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName});
}}
else
{{
@@ -471,7 +474,6 @@ private string GenerateFastPathFuncForDictionary(
private string GenerateForObject(TypeGenerationSpec typeMetadata)
{
- string typeCompilableName = typeMetadata.TypeRef;
string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
string createObjectFuncTypeArg = typeMetadata.ConstructionStrategy == ObjectConstructionStrategy.ParameterlessConstructor
@@ -486,11 +488,9 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)
string? serializeFuncSource = null;
string serializeFuncNamedArg;
- List? properties = typeMetadata.PropertiesMetadata;
-
if (typeMetadata.GenerateMetadata)
{
- propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, properties);
+ propMetadataInitFuncSource = GeneratePropMetadataInitFunc(typeMetadata.IsValueType, propInitMethodName, typeMetadata.PropertyGenSpecList!);
propMetadataInitFuncNamedArg = $@"propInitFunc: {propInitMethodName}";
}
else
@@ -500,13 +500,7 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)
if (typeMetadata.GenerateSerializationLogic)
{
- serializeFuncSource = GenerateFastPathFuncForObject(
- typeCompilableName,
- serializeMethodName,
- typeMetadata.CanBeNull,
- typeMetadata.ImplementsIJsonOnSerialized,
- typeMetadata.ImplementsIJsonOnSerializing,
- properties);
+ serializeFuncSource = GenerateFastPathFuncForObject(typeMetadata, serializeMethodName);
serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}";
}
else
@@ -514,14 +508,12 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)
serializeFuncNamedArg = @"serializeFunc: null";
}
- string objectInfoInitSource = $@"{JsonTypeInfoTypeRef}<{typeCompilableName}> objectInfo = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeCompilableName}>(
+ string objectInfoInitSource = $@"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeMetadata.TypeRef}>(
{OptionsInstanceVariableName},
{createObjectFuncTypeArg},
{propMetadataInitFuncNamedArg},
{GetNumberHandlingAsStr(typeMetadata.NumberHandling)},
- {serializeFuncNamedArg});
-
- _{typeFriendlyName} = objectInfo;";
+ {serializeFuncNamedArg});";
string additionalSource;
if (propMetadataInitFuncSource == null || serializeFuncSource == null)
@@ -539,14 +531,16 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)
private string GeneratePropMetadataInitFunc(
bool declaringTypeIsValueType,
string propInitMethodName,
- List? properties)
+ List properties)
{
const string PropVarName = "properties";
const string JsonContextVarName = "jsonContext";
+ int propCount = properties.Count;
+
string propertyArrayInstantiationValue = properties == null
? $"{ArrayTypeRef}.Empty<{JsonPropertyInfoTypeRef}>()"
- : $"new {JsonPropertyInfoTypeRef}[{properties.Count}]";
+ : $"new {JsonPropertyInfoTypeRef}[{propCount}]";
string contextTypeRef = _currentContext.ContextTypeRef;
@@ -562,72 +556,72 @@ private string GeneratePropMetadataInitFunc(
{JsonPropertyInfoTypeRef}[] {PropVarName} = {propertyArrayInstantiationValue};
");
- if (properties != null)
+ for (int i = 0; i < propCount; i++)
{
- for (int i = 0; i < properties.Count; i++)
- {
- PropertyGenerationSpec memberMetadata = properties[i];
+ PropertyGenerationSpec memberMetadata = properties[i];
- TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec;
+ TypeGenerationSpec memberTypeMetadata = memberMetadata.TypeGenerationSpec;
- string clrPropertyName = memberMetadata.ClrName;
+ string clrPropertyName = memberMetadata.ClrName;
- string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef;
+ string declaringTypeCompilableName = memberMetadata.DeclaringTypeRef;
- string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
- ? "null"
- : $"{JsonContextVarName}.{memberTypeMetadata.TypeInfoPropertyName}";
+ string memberTypeFriendlyName = memberTypeMetadata.ClassType == ClassType.TypeUnsupportedBySourceGen
+ ? "null"
+ : $"{JsonContextVarName}.{memberTypeMetadata.TypeInfoPropertyName}";
- string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}";
+ string typeTypeInfoNamedArg = $"propertyTypeInfo: {memberTypeFriendlyName}";
- string jsonPropertyNameNamedArg = memberMetadata.JsonPropertyName != null
- ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}"""
- : "jsonPropertyName: null";
+ string jsonPropertyNameNamedArg = memberMetadata.JsonPropertyName != null
+ ? @$"jsonPropertyName: ""{memberMetadata.JsonPropertyName}"""
+ : "jsonPropertyName: null";
- string getterNamedArg = memberMetadata.CanUseGetter
- ? $"getter: static (obj) => {{ return (({declaringTypeCompilableName})obj).{clrPropertyName}; }}"
- : "getter: null";
+ string getterNamedArg = memberMetadata.CanUseGetter
+ ? $"getter: static (obj) => (({declaringTypeCompilableName})obj).{clrPropertyName}"
+ : "getter: null";
- string setterNamedArg;
- if (memberMetadata.CanUseSetter)
- {
- string propMutation = declaringTypeIsValueType
- ? @$"{{ {UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value; }}"
- : $@"{{ (({declaringTypeCompilableName})obj).{clrPropertyName} = value; }}";
+ string setterNamedArg;
+ if (memberMetadata.CanUseSetter)
+ {
+ string propMutation = declaringTypeIsValueType
+ ? @$"{UnsafeTypeRef}.Unbox<{declaringTypeCompilableName}>(obj).{clrPropertyName} = value"
+ : $@"(({declaringTypeCompilableName})obj).{clrPropertyName} = value";
- setterNamedArg = $"setter: static (obj, value) => {propMutation}";
- }
- else
- {
- setterNamedArg = "setter: null";
- }
+ setterNamedArg = $"setter: static (obj, value) => {propMutation}";
+ }
+ else
+ {
+ setterNamedArg = "setter: null";
+ }
- JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition;
- string ignoreConditionNamedArg = ignoreCondition.HasValue
- ? $"ignoreCondition: JsonIgnoreCondition.{ignoreCondition.Value}"
- : "ignoreCondition: default";
+ JsonIgnoreCondition? ignoreCondition = memberMetadata.DefaultIgnoreCondition;
+ string ignoreConditionNamedArg = ignoreCondition.HasValue
+ ? $"ignoreCondition: {JsonIgnoreConditionTypeRef}.{ignoreCondition.Value}"
+ : "ignoreCondition: null";
- string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null
- ? "converter: null"
- : $"converter: {memberMetadata.ConverterInstantiationLogic}";
+ string converterNamedArg = memberMetadata.ConverterInstantiationLogic == null
+ ? "converter: null"
+ : $"converter: {memberMetadata.ConverterInstantiationLogic}";
- string memberTypeCompilableName = memberTypeMetadata.TypeRef;
+ string memberTypeCompilableName = memberTypeMetadata.TypeRef;
- sb.Append($@"
+ sb.Append($@"
{PropVarName}[{i}] = {JsonMetadataServicesTypeRef}.CreatePropertyInfo<{memberTypeCompilableName}>(
options,
- isProperty: {memberMetadata.IsProperty.ToString().ToLowerInvariant()},
+ isProperty: {ToCSharpKeyword(memberMetadata.IsProperty)},
+ isPublic: {ToCSharpKeyword(memberMetadata.IsPublic)},
+ isVirtual: {ToCSharpKeyword(memberMetadata.IsVirtual)},
declaringType: typeof({memberMetadata.DeclaringTypeRef}),
{typeTypeInfoNamedArg},
{converterNamedArg},
{getterNamedArg},
{setterNamedArg},
{ignoreConditionNamedArg},
+ hasJsonInclude: {ToCSharpKeyword(memberMetadata.HasJsonInclude)},
numberHandling: {GetNumberHandlingAsStr(memberMetadata.NumberHandling)},
propertyName: ""{clrPropertyName}"",
{jsonPropertyNameNamedArg});
");
- }
}
sb.Append(@$"
@@ -637,24 +631,29 @@ private string GeneratePropMetadataInitFunc(
return sb.ToString();
}
- private string GenerateFastPathFuncForObject(
- string typeInfoTypeRef,
- string serializeMethodName,
- bool canBeNull,
- bool implementsIJsonOnSerialized,
- bool implementsIJsonOnSerializing,
- List? properties)
+ private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec, string serializeMethodName)
{
JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions;
+ string typeRef = typeGenSpec.TypeRef;
- // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation.
- string[] runtimePropNames = GetRuntimePropNames(properties, options.PropertyNamingPolicy);
- _currentContext.RuntimePropertyNames.UnionWith(runtimePropNames);
+ if (!typeGenSpec.TryFilterSerializableProps(
+ options,
+ out Dictionary? serializableProperties,
+ out bool castingRequiredForProps))
+ {
+ string exceptionMessage = @$"""Invalid serializable-property configuration specified for type '{typeRef}'. For more information, use 'JsonSourceGenerationMode.Serialization'.""";
+
+ return GenerateFastPathFuncForType(
+ serializeMethodName,
+ typeRef,
+ $@"throw new {InvalidOperationExceptionTypeRef}({exceptionMessage});",
+ canBeNull: false); // Skip null check since we want to throw an exception straightaway.
+ }
StringBuilder sb = new();
- // Begin method definition
- if (implementsIJsonOnSerializing)
+ // Begin method logic.
+ if (typeGenSpec.ImplementsIJsonOnSerializing)
{
sb.Append($@"(({IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();");
sb.Append($@"{Environment.NewLine} ");
@@ -662,98 +661,119 @@ private string GenerateFastPathFuncForObject(
sb.Append($@"{WriterVarName}.WriteStartObject();");
- if (properties != null)
+ // Provide generation logic for each prop.
+ Debug.Assert(serializableProperties != null);
+
+ foreach (PropertyGenerationSpec propertyGenSpec in serializableProperties.Values)
{
- // Provide generation logic for each prop.
- for (int i = 0; i < properties.Count; i++)
+ if (!ShouldIncludePropertyForFastPath(propertyGenSpec, options))
{
- PropertyGenerationSpec propertySpec = properties[i];
- TypeGenerationSpec propertyTypeSpec = propertySpec.TypeGenerationSpec;
-
- if (propertyTypeSpec.ClassType == ClassType.TypeUnsupportedBySourceGen)
- {
- continue;
- }
+ continue;
+ }
- if (propertySpec.IsReadOnly)
- {
- if (propertySpec.IsProperty)
- {
- if (options.IgnoreReadOnlyProperties)
- {
- continue;
- }
- }
- else if (options.IgnoreReadOnlyFields)
- {
- continue;
- }
- }
+ TypeGenerationSpec propertyTypeSpec = propertyGenSpec.TypeGenerationSpec;
- if (!propertySpec.IsProperty && !propertySpec.HasJsonInclude && !options.IncludeFields)
- {
- continue;
- }
+ string runtimePropName = propertyGenSpec.RuntimePropertyName;
- Type propertyType = propertyTypeSpec.Type;
- string propName = $"{runtimePropNames[i]}PropName";
- string propValue = $"{ValueVarName}.{propertySpec.ClrName}";
- string methodArgs = $"{propName}, {propValue}";
+ // Add the property names to the context-wide cache; we'll generate the source to initialize them at the end of generation.
+ _currentContext.RuntimePropertyNames.Add(runtimePropName);
- string? methodToCall = GetWriterMethod(propertyType);
+ Type propertyType = propertyTypeSpec.Type;
+ string propName = $"{runtimePropName}PropName";
+ string? objectRef = castingRequiredForProps ? $"(({propertyGenSpec.DeclaringTypeRef}){ValueVarName})" : ValueVarName;
+ string propValue = $"{objectRef}.{propertyGenSpec.ClrName}";
+ string methodArgs = $"{propName}, {propValue}";
- if (propertyType == _generationSpec.CharType)
- {
- methodArgs = $"{methodArgs}.ToString()";
- }
+ string? methodToCall = GetWriterMethod(propertyType);
- string serializationLogic;
+ if (propertyType == _generationSpec.CharType)
+ {
+ methodArgs = $"{methodArgs}.ToString()";
+ }
- if (methodToCall != null)
- {
- serializationLogic = $@"
- {methodToCall}({methodArgs});";
- }
- else
- {
- serializationLogic = $@"
- {WriterVarName}.WritePropertyName({propName});
- {GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}";
- }
+ string serializationLogic;
- JsonIgnoreCondition ignoreCondition = propertySpec.DefaultIgnoreCondition ?? options.DefaultIgnoreCondition;
- DefaultCheckType defaultCheckType;
- bool typeCanBeNull = propertyTypeSpec.CanBeNull;
+ if (methodToCall != null)
+ {
+ serializationLogic = $@"
+ {methodToCall}({methodArgs});";
+ }
+ else
+ {
+ serializationLogic = $@"
+ {WriterVarName}.WritePropertyName({propName});
+ {GetSerializeLogicForNonPrimitiveType(propertyTypeSpec.TypeInfoPropertyName, propValue, propertyTypeSpec.GenerateSerializationLogic)}";
+ }
- switch (ignoreCondition)
- {
- case JsonIgnoreCondition.WhenWritingNull:
- defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.None;
- break;
- case JsonIgnoreCondition.WhenWritingDefault:
- defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default;
- break;
- default:
- defaultCheckType = DefaultCheckType.None;
- break;
- }
+ JsonIgnoreCondition ignoreCondition = propertyGenSpec.DefaultIgnoreCondition ?? options.DefaultIgnoreCondition;
+ DefaultCheckType defaultCheckType;
+ bool typeCanBeNull = propertyTypeSpec.CanBeNull;
- sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, defaultCheckType));
+ switch (ignoreCondition)
+ {
+ case JsonIgnoreCondition.WhenWritingNull:
+ defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.None;
+ break;
+ case JsonIgnoreCondition.WhenWritingDefault:
+ defaultCheckType = typeCanBeNull ? DefaultCheckType.Null : DefaultCheckType.Default;
+ break;
+ default:
+ defaultCheckType = DefaultCheckType.None;
+ break;
}
+
+ sb.Append(WrapSerializationLogicInDefaultCheckIfRequired(serializationLogic, propValue, propertyTypeSpec.TypeRef, defaultCheckType));
}
- // End method definition
+ // End method logic.
sb.Append($@"
- {WriterVarName}.WriteEndObject();");
+ {WriterVarName}.WriteEndObject();");
- if (implementsIJsonOnSerialized)
+ if (typeGenSpec.ImplementsIJsonOnSerialized)
{
sb.Append($@"{Environment.NewLine} ");
sb.Append($@"(({IJsonOnSerializedFullName}){ValueVarName}).OnSerialized();");
};
- return GenerateFastPathFuncForType(serializeMethodName, typeInfoTypeRef, sb.ToString(), canBeNull);
+ return GenerateFastPathFuncForType(serializeMethodName, typeRef, sb.ToString(), typeGenSpec.CanBeNull);
+ }
+
+ private static bool ShouldIncludePropertyForFastPath(PropertyGenerationSpec propertyGenSpec, JsonSourceGenerationOptionsAttribute options)
+ {
+ TypeGenerationSpec propertyTypeSpec = propertyGenSpec.TypeGenerationSpec;
+
+ if (propertyTypeSpec.ClassType == ClassType.TypeUnsupportedBySourceGen || !propertyGenSpec.CanUseGetter)
+ {
+ return false;
+ }
+
+ if (!propertyGenSpec.IsProperty && !propertyGenSpec.HasJsonInclude && !options.IncludeFields)
+ {
+ return false;
+ }
+
+ if (propertyGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
+ {
+ return false;
+ }
+
+ if (propertyGenSpec.IsReadOnly)
+ {
+ if (propertyGenSpec.IsProperty)
+ {
+ if (options.IgnoreReadOnlyProperties)
+ {
+ return false;
+ }
+ }
+ else if (options.IgnoreReadOnlyFields)
+ {
+ return false;
+ }
+ }
+
+ return true;
}
private string? GetWriterMethod(Type type)
@@ -829,62 +849,28 @@ private enum DefaultCheckType
Default,
}
- private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, DefaultCheckType defaultCheckType)
- {
- if (defaultCheckType == DefaultCheckType.None)
- {
- return serializationLogic;
- }
-
- string defaultLiteral = defaultCheckType == DefaultCheckType.Null ? "null" : "default";
- return $@"
- if ({propValue} != {defaultLiteral})
- {{{serializationLogic}
- }}";
- }
-
- private string[] GetRuntimePropNames(List? properties, JsonKnownNamingPolicy namingPolicy)
+ private string WrapSerializationLogicInDefaultCheckIfRequired(string serializationLogic, string propValue, string propTypeRef, DefaultCheckType defaultCheckType)
{
- if (properties == null)
- {
- return Array.Empty();
- }
-
- int propCount = properties.Count;
- string[] runtimePropNames = new string[propCount];
+ string comparisonLogic;
- // Compute JsonEncodedText values to represent each property name. This gives the best throughput performance
- for (int i = 0; i < propCount; i++)
+ switch (defaultCheckType)
{
- PropertyGenerationSpec propertySpec = properties[i];
-
- string propName = DetermineRuntimePropName(propertySpec.ClrName, propertySpec.JsonPropertyName, namingPolicy);
- Debug.Assert(propName != null);
-
- runtimePropNames[i] = propName;
- }
-
- return runtimePropNames;
- }
-
- private string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy)
- {
- string runtimePropName;
-
- if (jsonPropName != null)
- {
- runtimePropName = jsonPropName;
- }
- else if (namingPolicy == JsonKnownNamingPolicy.CamelCase)
- {
- runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName);
- }
- else
- {
- runtimePropName = clrPropName;
+ case DefaultCheckType.None:
+ return serializationLogic;
+ case DefaultCheckType.Null:
+ comparisonLogic = $"{propValue} != null";
+ break;
+ case DefaultCheckType.Default:
+ comparisonLogic = $"!{EqualityComparerTypeRef}<{propTypeRef}>.Default.Equals(default, {propValue})";
+ break;
+ default:
+ throw new InvalidOperationException();
}
- return runtimePropName;
+ return $@"
+ if ({comparisonLogic})
+ {{{IndentSource(serializationLogic, numIndentations: 1)}
+ }}";
}
private string GenerateForType(TypeGenerationSpec typeMetadata, string metadataInitSource, string? additionalSource = null)
@@ -964,10 +950,10 @@ private string GetLogicForDefaultSerializerOptionsInit()
private static {JsonSerializerOptionsTypeRef} {DefaultOptionsStaticVarName} {{ get; }} = new {JsonSerializerOptionsTypeRef}()
{{
DefaultIgnoreCondition = {JsonIgnoreConditionTypeRef}.{options.DefaultIgnoreCondition},
- IgnoreReadOnlyFields = {options.IgnoreReadOnlyFields.ToString().ToLowerInvariant()},
- IgnoreReadOnlyProperties = {options.IgnoreReadOnlyProperties.ToString().ToLowerInvariant()},
- IncludeFields = {options.IncludeFields.ToString().ToLowerInvariant()},
- WriteIndented = {options.WriteIndented.ToString().ToLowerInvariant()},{namingPolicyInit}
+ IgnoreReadOnlyFields = {ToCSharpKeyword(options.IgnoreReadOnlyFields)},
+ IgnoreReadOnlyProperties = {ToCSharpKeyword(options.IgnoreReadOnlyProperties)},
+ IncludeFields = {ToCSharpKeyword(options.IncludeFields)},
+ WriteIndented = {ToCSharpKeyword(options.WriteIndented)},{namingPolicyInit}
}};";
}
@@ -1013,8 +999,11 @@ private string GetGetTypeInfoImplementation()
sb.Append(@$"public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type)
{{");
+ HashSet types = new(_currentContext.RootSerializableTypes);
+ types.UnionWith(_currentContext.NullableUnderlyingTypes);
+
// TODO (https://github.com/dotnet/runtime/issues/52218): Make this Dictionary-lookup-based if root-serializable type count > 64.
- foreach (TypeGenerationSpec metadata in _currentContext.RootSerializableTypes)
+ foreach (TypeGenerationSpec metadata in types)
{
if (metadata.ClassType != ClassType.TypeUnsupportedBySourceGen)
{
@@ -1065,5 +1054,7 @@ private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling)
private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
}
+
+ private static string ToCSharpKeyword(bool value) => value.ToString().ToLowerInvariant();
}
}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
index 70c8a412b6e59..cefb9de2d7164 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
@@ -4,15 +4,15 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Reflection;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.Text;
+using System.Text.Json.Reflection;
using System.Text.Json.Serialization;
-using System.Text.Json.SourceGeneration.Reflection;
-using System.Linq;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
namespace System.Text.Json.SourceGeneration
{
@@ -62,6 +62,10 @@ private sealed class Parser
///
private readonly Dictionary _typeGenerationSpecCache = new();
+ private readonly HashSet _nullableTypeGenerationSpecCache = new();
+
+ private JsonKnownNamingPolicy _currentContextNamingPolicy;
+
private static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor(
id: "SYSLIB1032",
title: new LocalizableResourceString(nameof(SR.ContextClassesMustBePartialTitle), SR.ResourceManager, typeof(FxResources.System.Text.Json.SourceGeneration.SR)),
@@ -164,7 +168,10 @@ public Parser(in GeneratorExecutionContext executionContext)
ContextClassDeclarationList = classDeclarationList
};
- foreach(AttributeSyntax attribute in serializableAttributeList)
+ // Set the naming policy for the current context.
+ _currentContextNamingPolicy = contextGenSpec.GenerationOptions.PropertyNamingPolicy;
+
+ foreach (AttributeSyntax attribute in serializableAttributeList)
{
TypeGenerationSpec? metadata = GetRootSerializableType(compilationSemanticModel, attribute, contextGenSpec.GenerationOptions.GenerationMode);
if (metadata != null)
@@ -178,11 +185,14 @@ public Parser(in GeneratorExecutionContext executionContext)
continue;
}
+ contextGenSpec.NullableUnderlyingTypes.UnionWith(_nullableTypeGenerationSpecCache);
+
contextGenSpecList ??= new List();
contextGenSpecList.Add(contextGenSpec);
// Clear the cache of generated metadata between the processing of context classes.
_typeGenerationSpecCache.Clear();
+ _nullableTypeGenerationSpecCache.Clear();
}
if (contextGenSpecList == null)
@@ -231,7 +241,7 @@ private bool DerivesFromJsonSerializerContext(
return match != null;
}
- private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhenAttribute(true)] out List? classDeclarationList)
+ private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out List? classDeclarationList)
{
INamedTypeSymbol currentSymbol = typeSymbol;
classDeclarationList = null;
@@ -479,14 +489,14 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
}
// Add metadata to cache now to prevent stack overflow when the same type is found somewhere else in the object graph.
- typeMetadata = new();
+ typeMetadata = new TypeGenerationSpec();
_typeGenerationSpecCache[type] = typeMetadata;
ClassType classType;
Type? collectionKeyType = null;
Type? collectionValueType = null;
- Type? nullableUnderlyingType = null;
- List? propertiesMetadata = null;
+ TypeGenerationSpec? nullableUnderlyingTypeGenSpec = null;
+ List? propGenSpecList = null;
CollectionType collectionType = CollectionType.NotApplicable;
ObjectConstructionStrategy constructionStrategy = default;
JsonNumberHandling? numberHandling = null;
@@ -524,10 +534,12 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
{
classType = ClassType.KnownType;
}
- else if (type.IsNullableValueType(_nullableOfTType, out nullableUnderlyingType))
+ else if (type.IsNullableValueType(_nullableOfTType, out Type? nullableUnderlyingType))
{
Debug.Assert(nullableUnderlyingType != null);
classType = ClassType.Nullable;
+ nullableUnderlyingTypeGenSpec = GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode);
+ _nullableTypeGenerationSpecCache.Add(nullableUnderlyingTypeGenSpec);
}
else if (type.IsEnum)
{
@@ -585,38 +597,40 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializedFullName) != null;
implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == IJsonOnSerializingFullName) != null;
+ propGenSpecList = new List();
+ Dictionary? ignoredMembers = null;
+
+ const BindingFlags bindingFlags =
+ BindingFlags.Instance |
+ BindingFlags.Public |
+ BindingFlags.NonPublic |
+ BindingFlags.DeclaredOnly;
+
for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
{
- const BindingFlags bindingFlags =
- BindingFlags.Instance |
- BindingFlags.Public |
- BindingFlags.NonPublic |
- BindingFlags.DeclaredOnly;
-
foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
{
- PropertyGenerationSpec metadata = GetPropertyGenerationSpec(propertyInfo, generationMode);
+ bool isVirtual = propertyInfo.IsVirtual();
- // Ignore indexers.
- if (propertyInfo.GetIndexParameters().Length > 0)
+ if (propertyInfo.GetIndexParameters().Length > 0 ||
+ PropertyIsOverridenAndIgnored(propertyInfo.Name, propertyInfo.PropertyType, isVirtual, ignoredMembers))
{
continue;
}
- if (metadata.CanUseGetter || metadata.CanUseSetter)
- {
- (propertiesMetadata ??= new()).Add(metadata);
- }
+ PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode);
+ CacheMember(spec, ref propGenSpecList, ref ignoredMembers);
}
foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags))
{
- PropertyGenerationSpec metadata = GetPropertyGenerationSpec(fieldInfo, generationMode);
-
- if (metadata.CanUseGetter || metadata.CanUseSetter)
+ if (PropertyIsOverridenAndIgnored(fieldInfo.Name, fieldInfo.FieldType, currentMemberIsVirtual: false, ignoredMembers))
{
- (propertiesMetadata ??= new()).Add(metadata);
+ continue;
}
+
+ PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode);
+ CacheMember(spec, ref propGenSpecList, ref ignoredMembers);
}
}
}
@@ -629,12 +643,12 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
classType,
isValueType: type.IsValueType,
numberHandling,
- propertiesMetadata,
+ propGenSpecList,
collectionType,
collectionKeyTypeMetadata: collectionKeyType != null ? GetOrAddTypeGenerationSpec(collectionKeyType, generationMode) : null,
collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType, generationMode) : null,
constructionStrategy,
- nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode) : null,
+ nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec,
converterInstatiationLogic,
implementsIJsonOnSerialized,
implementsIJsonOnSerializing);
@@ -642,7 +656,37 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
return typeMetadata;
}
- private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, JsonSourceGenerationMode generationMode)
+ private void CacheMember(
+ PropertyGenerationSpec propGenSpec,
+ ref List propGenSpecList,
+ ref Dictionary ignoredMembers)
+ {
+ propGenSpecList.Add(propGenSpec);
+
+ if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
+ {
+ ignoredMembers ??= new Dictionary();
+ ignoredMembers.Add(propGenSpec.ClrName, propGenSpec);
+ }
+ }
+
+ private static bool PropertyIsOverridenAndIgnored(
+ string currentMemberName,
+ Type currentMemberType,
+ bool currentMemberIsVirtual,
+ Dictionary? ignoredMembers)
+ {
+ if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMemberName, out PropertyGenerationSpec? ignoredMember))
+ {
+ return false;
+ }
+
+ return currentMemberType == ignoredMember.TypeGenerationSpec.Type &&
+ currentMemberIsVirtual &&
+ ignoredMember.IsVirtual;
+ }
+
+ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, bool isVirtual, JsonSourceGenerationMode generationMode)
{
IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo);
@@ -709,8 +753,9 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
Type memberCLRType;
bool isReadOnly;
- bool canUseGetter;
- bool canUseSetter;
+ bool isPublic = false;
+ bool canUseGetter = false;
+ bool canUseSetter = false;
bool getterIsVirtual = false;
bool setterIsVirtual = false;
@@ -718,33 +763,75 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
{
case PropertyInfo propertyInfo:
{
- MethodInfo setMethod = propertyInfo.SetMethod;
memberCLRType = propertyInfo.PropertyType;
- isReadOnly = setMethod == null;
- canUseGetter = PropertyAccessorCanBeReferenced(propertyInfo.GetMethod, hasJsonInclude);
- canUseSetter = PropertyAccessorCanBeReferenced(setMethod, hasJsonInclude) && !setMethod.IsInitOnly();
- getterIsVirtual = propertyInfo.GetMethod?.IsVirtual == true;
- setterIsVirtual = propertyInfo.SetMethod?.IsVirtual == true;
+
+ MethodInfo? getMethod = propertyInfo.GetMethod;
+ MethodInfo? setMethod = propertyInfo.SetMethod;
+
+ if (getMethod != null)
+ {
+ if (getMethod.IsPublic)
+ {
+ isPublic = true;
+ canUseGetter = true;
+ }
+ else if (getMethod.IsAssembly)
+ {
+ canUseGetter = hasJsonInclude;
+ }
+
+ getterIsVirtual = getMethod.IsVirtual;
+ }
+
+ if (setMethod != null)
+ {
+ isReadOnly = false;
+
+ if (setMethod.IsPublic)
+ {
+ isPublic = true;
+ canUseSetter = !setMethod.IsInitOnly();
+ }
+ else if (setMethod.IsAssembly)
+ {
+ canUseSetter = hasJsonInclude && !setMethod.IsInitOnly();
+ }
+
+ setterIsVirtual = setMethod.IsVirtual;
+ }
+ else
+ {
+ isReadOnly = true;
+ }
}
break;
case FieldInfo fieldInfo:
{
- Debug.Assert(fieldInfo.IsPublic);
memberCLRType = fieldInfo.FieldType;
+ isPublic = fieldInfo.IsPublic;
isReadOnly = fieldInfo.IsInitOnly;
- canUseGetter = true;
- canUseSetter = !isReadOnly;
+
+ if (!fieldInfo.IsPrivate && !fieldInfo.IsFamily)
+ {
+ canUseGetter = true;
+ canUseSetter = !isReadOnly;
+ }
}
break;
default:
throw new InvalidOperationException();
}
+ string clrName = memberInfo.Name;
+
return new PropertyGenerationSpec
{
- ClrName = memberInfo.Name,
+ ClrName = clrName,
IsProperty = memberInfo.MemberType == MemberTypes.Property,
+ IsPublic = isPublic,
+ IsVirtual = isVirtual,
JsonPropertyName = jsonPropertyName,
+ RuntimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy),
IsReadOnly = isReadOnly,
CanUseGetter = canUseGetter,
CanUseSetter = canUseSetter,
@@ -759,8 +846,8 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
};
}
- private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor, bool hasJsonInclude) =>
- (memberAccessor != null && !memberAccessor.IsPrivate) && (memberAccessor.IsPublic || hasJsonInclude);
+ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor)
+ => accessor != null && (accessor.IsPublic || accessor.IsAssembly);
private string? GetConverterInstantiationLogic(CustomAttributeData attributeData)
{
@@ -780,6 +867,26 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? memberAccessor,
return $"new {converterType.GetUniqueCompilableTypeName()}()";
}
+ private static string DetermineRuntimePropName(string clrPropName, string? jsonPropName, JsonKnownNamingPolicy namingPolicy)
+ {
+ string runtimePropName;
+
+ if (jsonPropName != null)
+ {
+ runtimePropName = jsonPropName;
+ }
+ else if (namingPolicy == JsonKnownNamingPolicy.CamelCase)
+ {
+ runtimePropName = JsonNamingPolicy.CamelCase.ConvertName(clrPropName);
+ }
+ else
+ {
+ runtimePropName = clrPropName;
+ }
+
+ return runtimePropName;
+ }
+
private void PopulateNumberTypes()
{
Debug.Assert(_numberTypes != null);
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
index 7e8c45ff4299c..399b893e8e8e9 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.cs
@@ -5,7 +5,7 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
-using System.Text.Json.SourceGeneration.Reflection;
+using System.Text.Json.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -37,6 +37,12 @@ public void Initialize(GeneratorInitializationContext context)
///
public void Execute(GeneratorExecutionContext executionContext)
{
+#if LAUNCH_DEBUGGER
+ if (!Diagnostics.Debugger.IsAttached)
+ {
+ Diagnostics.Debugger.Launch();
+ }
+#endif
SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver;
List? contextClasses = receiver.ClassDeclarationSyntaxList;
if (contextClasses == null)
@@ -55,12 +61,6 @@ public void Execute(GeneratorExecutionContext executionContext)
}
}
- ///
- /// Helper for unit tests.
- ///
- public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type);
- private List? _rootTypes;
-
private sealed class SyntaxReceiver : ISyntaxReceiver
{
public List? ClassDeclarationSyntaxList { get; private set; }
@@ -73,5 +73,11 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
}
}
}
+
+ ///
+ /// Helper for unit tests.
+ ///
+ public Dictionary? GetSerializableTypes() => _rootTypes?.ToDictionary(p => p.Type.FullName, p => p.Type);
+ private List? _rootTypes;
}
}
diff --git a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs
index 25ae73692ecb0..5d52ae2e3553e 100644
--- a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs
+++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs
@@ -9,9 +9,6 @@ namespace System.Text.Json.SourceGeneration
[DebuggerDisplay("Name={Name}, Type={TypeMetadata}")]
internal sealed class PropertyGenerationSpec
{
- ///
- /// The CLR name of the property.
- ///
public string ClrName { get; init; }
///
@@ -19,11 +16,22 @@ internal sealed class PropertyGenerationSpec
///
public bool IsProperty { get; init; }
+ public bool IsPublic { get; init; }
+
+ public bool IsVirtual { get; init; }
+
///
/// The property name specified via JsonPropertyNameAttribute, if available.
///
public string? JsonPropertyName { get; init; }
+ ///
+ /// The pre-determined JSON property name, accounting for
+ /// specified ahead-of-time via .
+ /// Only used in fast-path serialization logic.
+ ///
+ public string RuntimePropertyName { get; init; }
+
///
/// Whether the property has a set method.
///
diff --git a/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs
index d5ed28e4800a1..a3282b6984439 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/AssemblyWrapper.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class AssemblyWrapper : Assembly
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs
index 979d32c8f7c99..42d167a462207 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/ConstructorInfoWrapper.cs
@@ -6,7 +6,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class ConstructorInfoWrapper : ConstructorInfo
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs
index 75b31db587a8f..e518212ce65b0 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/CustomAttributeDataWrapper.cs
@@ -6,7 +6,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class CustomAttributeDataWrapper : CustomAttributeData
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs
index cf36ceacbeee5..66e00c0de20bb 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs
@@ -6,7 +6,7 @@
using Microsoft.CodeAnalysis;
using System.Globalization;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class FieldInfoWrapper : FieldInfo
{
@@ -33,6 +33,11 @@ public override FieldAttributes Attributes
_attributes |= FieldAttributes.Static;
}
+ if (_field.IsReadOnly)
+ {
+ _attributes |= FieldAttributes.InitOnly;
+ }
+
switch (_field.DeclaredAccessibility)
{
case Accessibility.Public:
@@ -41,6 +46,9 @@ public override FieldAttributes Attributes
case Accessibility.Private:
_attributes |= FieldAttributes.Private;
break;
+ case Accessibility.Protected:
+ _attributes |= FieldAttributes.Family;
+ break;
}
}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs
index 1b4de7811fd74..ccfd0fb7c6e4c 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/MemberInfoWrapper.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class MemberInfoWrapper : MemberInfo
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
index 885885325fe53..d12f3f8ed60f8 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs
@@ -8,7 +8,7 @@
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class MetadataLoadContextInternal
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs
index 44eecf0b5919d..637483847e599 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/MethodInfoWrapper.cs
@@ -6,7 +6,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class MethodInfoWrapper : MethodInfo
{
@@ -41,7 +41,7 @@ public override MethodAttributes Attributes
_attributes |= MethodAttributes.Static;
}
- if (_method.IsVirtual)
+ if (_method.IsVirtual || _method.IsOverride)
{
_attributes |= MethodAttributes.Virtual;
}
@@ -54,6 +54,9 @@ public override MethodAttributes Attributes
case Accessibility.Private:
_attributes |= MethodAttributes.Private;
break;
+ case Accessibility.Internal:
+ _attributes |= MethodAttributes.Assembly;
+ break;
}
}
diff --git a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs
index b8891251c7e62..ad9091017728f 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/ParameterInfoWrapper.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class ParameterInfoWrapper : ParameterInfo
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs
index 294c6dd105916..bfb593f992fbd 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs
@@ -6,7 +6,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class PropertyInfoWrapper : PropertyInfo
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs
index 1c0619ddda58e..599cae49aebd5 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs
@@ -5,9 +5,9 @@
using System.Linq;
using System.Reflection;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
- internal static class ReflectionExtensions
+ internal static partial class ReflectionExtensions
{
public static CustomAttributeData GetCustomAttributeData(this MemberInfo memberInfo, Type type)
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
index 610e7fe4eef6a..3dd1e925e2f19 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs
@@ -5,7 +5,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal static class RoslynExtensions
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs
index 8ff487ab0c6ee..6ad76f42b7b4a 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/TypeExtensions.cs
@@ -4,7 +4,7 @@
using System.Diagnostics;
using System.Linq;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal static class TypeExtensions
{
diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs
index 2ffc4725922ab..abeed3dfaaf0d 100644
--- a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs
+++ b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs
@@ -9,7 +9,7 @@
using System.Reflection;
using Microsoft.CodeAnalysis;
-namespace System.Text.Json.SourceGeneration.Reflection
+namespace System.Text.Json.Reflection
{
internal class TypeWrapper : Type
{
@@ -231,18 +231,32 @@ public override FieldInfo GetField(string name, BindingFlags bindingAttr)
public override FieldInfo[] GetFields(BindingFlags bindingAttr)
{
- var fields = new List();
+ List fields = new();
+
foreach (ISymbol item in _typeSymbol.GetMembers())
{
- // Associated Symbol checks the field is not a backingfield.
- if (item is IFieldSymbol field && field.AssociatedSymbol == null && !field.IsReadOnly)
+ if (item is IFieldSymbol fieldSymbol)
{
- if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public)
+ // Skip if:
+ if (
+ // this is a backing field
+ fieldSymbol.AssociatedSymbol != null ||
+ // we want a static field and this is not static
+ (BindingFlags.Static & bindingAttr) != 0 && !fieldSymbol.IsStatic ||
+ // we want an instance field and this is static or a constant
+ (BindingFlags.Instance & bindingAttr) != 0 && (fieldSymbol.IsStatic || fieldSymbol.IsConst))
+ {
+ continue;
+ }
+
+ if ((BindingFlags.Public & bindingAttr) != 0 && item.DeclaredAccessibility == Accessibility.Public ||
+ (BindingFlags.NonPublic & bindingAttr) != 0)
{
- fields.Add(new FieldInfoWrapper(field, _metadataLoadContext));
+ fields.Add(new FieldInfoWrapper(fieldSymbol, _metadataLoadContext));
}
}
}
+
return fields.ToArray();
}
@@ -300,18 +314,28 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr)
return nestedTypes.ToArray();
}
- // TODO: make sure to use bindingAttr for correctness. Current implementation assumes public and non-static.
public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
- var properties = new List();
+ List properties = new();
foreach (ISymbol item in _typeSymbol.GetMembers())
{
- if (item is IPropertySymbol property)
+ if (item is IPropertySymbol propertySymbol)
{
- if ((item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public)
+ // Skip if:
+ if (
+ // we want a static property and this is not static
+ (BindingFlags.Static & bindingAttr) != 0 && !propertySymbol.IsStatic ||
+ // we want an instance property and this is static
+ (BindingFlags.Instance & bindingAttr) != 0 && propertySymbol.IsStatic)
+ {
+ continue;
+ }
+
+ if ((BindingFlags.Public & bindingAttr) != 0 && item.DeclaredAccessibility == Accessibility.Public ||
+ (BindingFlags.NonPublic & bindingAttr) != 0)
{
- properties.Add(new PropertyInfoWrapper(property, _metadataLoadContext));
+ properties.Add(new PropertyInfoWrapper(propertySymbol, _metadataLoadContext));
}
}
}
diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
index 3e449e71aab1a..008a5b221895d 100644
--- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
+++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.csproj
@@ -13,6 +13,7 @@
$(DefineConstants);BUILDING_SOURCE_GENERATOR
+ $(DefineConstants);LAUNCH_DEBUGGER
@@ -27,12 +28,14 @@
+
+
diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
index 36a0c58a17a31..865134823f9a3 100644
--- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
+++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
@@ -3,8 +3,9 @@
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Reflection;
using System.Text.Json.Serialization;
-using System.Text.Json.SourceGeneration.Reflection;
namespace System.Text.Json.SourceGeneration
{
@@ -42,7 +43,7 @@ internal class TypeGenerationSpec
public JsonNumberHandling? NumberHandling { get; private set; }
- public List? PropertiesMetadata { get; private set; }
+ public List? PropertyGenSpecList { get; private set; }
public CollectionType CollectionType { get; private set; }
@@ -64,7 +65,7 @@ public void Initialize(
ClassType classType,
bool isValueType,
JsonNumberHandling? numberHandling,
- List? propertiesMetadata,
+ List? propertyGenSpecList,
CollectionType collectionType,
TypeGenerationSpec? collectionKeyTypeMetadata,
TypeGenerationSpec? collectionValueTypeMetadata,
@@ -82,7 +83,7 @@ public void Initialize(
IsValueType = isValueType;
CanBeNull = !isValueType || nullableUnderlyingTypeMetadata != null;
NumberHandling = numberHandling;
- PropertiesMetadata = propertiesMetadata;
+ PropertyGenSpecList = propertyGenSpecList;
CollectionType = collectionType;
CollectionKeyTypeMetadata = collectionKeyTypeMetadata;
CollectionValueTypeMetadata = collectionValueTypeMetadata;
@@ -93,6 +94,86 @@ public void Initialize(
ImplementsIJsonOnSerializing = implementsIJsonOnSerializing;
}
+ public bool TryFilterSerializableProps(
+ JsonSourceGenerationOptionsAttribute options,
+ [NotNullWhen(true)] out Dictionary? serializableProperties,
+ out bool castingRequiredForProps)
+ {
+ serializableProperties = new Dictionary();
+ Dictionary? ignoredMembers = null;
+
+ for (int i = 0; i < PropertyGenSpecList.Count; i++)
+ {
+ PropertyGenerationSpec propGenSpec = PropertyGenSpecList[i];
+ bool hasJsonInclude = propGenSpec.HasJsonInclude;
+ JsonIgnoreCondition? ignoreCondition = propGenSpec.DefaultIgnoreCondition;
+
+ if (ignoreCondition == JsonIgnoreCondition.WhenWritingNull && !propGenSpec.TypeGenerationSpec.CanBeNull)
+ {
+ goto ReturnFalse;
+ }
+
+ if (!propGenSpec.IsPublic)
+ {
+ if (hasJsonInclude)
+ {
+ goto ReturnFalse;
+ }
+
+ continue;
+ }
+
+ if (!propGenSpec.IsProperty && !hasJsonInclude && !options.IncludeFields)
+ {
+ continue;
+ }
+
+ string memberName = propGenSpec.ClrName!;
+
+ // The JsonPropertyNameAttribute or naming policy resulted in a collision.
+ if (!serializableProperties.TryAdd(propGenSpec.RuntimePropertyName, propGenSpec))
+ {
+ PropertyGenerationSpec other = serializableProperties[propGenSpec.RuntimePropertyName]!;
+
+ if (other.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
+ {
+ // Overwrite previously cached property since it has [JsonIgnore].
+ serializableProperties[propGenSpec.RuntimePropertyName] = propGenSpec;
+ }
+ else if (
+ // Does the current property have `JsonIgnoreAttribute`?
+ propGenSpec.DefaultIgnoreCondition != JsonIgnoreCondition.Always &&
+ // Is the current property hidden by the previously cached property
+ // (with `new` keyword, or by overriding)?
+ other.ClrName != memberName &&
+ // Was a property with the same CLR name was ignored? That property hid the current property,
+ // thus, if it was ignored, the current property should be ignored too.
+ ignoredMembers?.ContainsKey(memberName) != true)
+ {
+ // We throw if we have two public properties that have the same JSON property name, and neither have been ignored.
+ serializableProperties = null;
+ castingRequiredForProps = false;
+ return false;
+ }
+ // Ignore the current property.
+ }
+
+ if (propGenSpec.DefaultIgnoreCondition == JsonIgnoreCondition.Always)
+ {
+ (ignoredMembers ??= new Dictionary()).Add(memberName, propGenSpec);
+ }
+ }
+
+ Debug.Assert(PropertyGenSpecList.Count >= serializableProperties.Count);
+ castingRequiredForProps = PropertyGenSpecList.Count > serializableProperties.Count;
+ return true;
+
+ReturnFalse:
+ serializableProperties = null;
+ castingRequiredForProps = false;
+ return false;
+ }
+
private bool FastPathIsSupported()
{
if (ClassType == ClassType.Object)
diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
index 2d74d3535c3fc..8e87154e80357 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -932,7 +932,7 @@ public static partial class JsonMetadataServices
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Func createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo keyInfo, System.Text.Json.Serialization.Metadata.JsonTypeInfo valueInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Dictionary where TKey : notnull { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.List { throw null; }
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Func? propInitFunc, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where T : notnull { throw null; }
- public static System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreatePropertyInfo(System.Text.Json.JsonSerializerOptions options, bool isProperty, System.Type declaringType, System.Text.Json.Serialization.Metadata.JsonTypeInfo propertyTypeInfo, System.Text.Json.Serialization.JsonConverter? converter, System.Func