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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ private static void PopulateProperties(JsonTypeInfo typeInfo)
}
}

private const BindingFlags AllInstanceMembers =
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly;

/// <summary>
/// Looks up the type for a member matching the given name and member type.
/// </summary>
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
internal static MemberInfo? LookupMemberInfo(Type type, MemberTypes memberType, string name)
{
Debug.Assert(memberType is MemberTypes.Field or MemberTypes.Property);

// Walk the type hierarchy starting from the current type up to the base type(s)
foreach (Type t in type.GetSortedTypeHierarchy())
{
MemberInfo[] members = t.GetMember(name, memberType, AllInstanceMembers);
if (members.Length > 0)
{
return members[0];
Comment thread
eiriktsarpalis marked this conversation as resolved.
}
}

return null;
}

[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
private static void AddMembersDeclaredBySuperType(
Expand All @@ -124,17 +151,11 @@ private static void AddMembersDeclaredBySuperType(
Debug.Assert(!typeInfo.IsReadOnly);
Debug.Assert(currentType.IsAssignableFrom(typeInfo.Type));

const BindingFlags BindingFlags =
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly;

// Compiler adds RequiredMemberAttribute to type if any of the members are marked with 'required' keyword.
bool shouldCheckMembersForRequiredMemberAttribute =
!constructorHasSetsRequiredMembersAttribute && currentType.HasRequiredMemberAttribute();

foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags))
foreach (PropertyInfo propertyInfo in currentType.GetProperties(AllInstanceMembers))
{
// Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d.
if (propertyInfo.GetIndexParameters().Length > 0 ||
Expand All @@ -160,7 +181,7 @@ private static void AddMembersDeclaredBySuperType(
}
}

foreach (FieldInfo fieldInfo in currentType.GetFields(BindingFlags))
foreach (FieldInfo fieldInfo in currentType.GetFields(AllInstanceMembers))
{
bool hasJsonIncludeAtribute = fieldInfo.GetCustomAttribute<JsonIncludeAttribute>(inherit: false) != null;
if (hasJsonIncludeAtribute || (fieldInfo.IsPublic && typeInfo.Options.IncludeFields))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ private static JsonPropertyInfo<T> CreatePropertyInfoCore<T>(JsonPropertyInfoVal
propertyInfo.IgnoreCondition = propertyInfoValues.IgnoreCondition;
propertyInfo.JsonTypeInfo = propertyInfoValues.PropertyTypeInfo;
propertyInfo.NumberHandling = propertyInfoValues.NumberHandling;
propertyInfo.IsSourceGenerated = true;

return propertyInfo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading;

namespace System.Text.Json.Serialization.Metadata
{
Expand Down Expand Up @@ -162,17 +163,39 @@ internal JsonIgnoreCondition? IgnoreCondition
/// </remarks>
public ICustomAttributeProvider? AttributeProvider
{
get => _attributeProvider;
get
{
ICustomAttributeProvider attributeProvider = _attributeProvider ?? InitializeAttributeProvider();
return ReferenceEquals(attributeProvider, s_nullAttributeProvider) ? null : attributeProvider;
}
set
{
VerifyMutable();

_attributeProvider = value;
_attributeProvider = value ?? s_nullAttributeProvider;
}
}

private JsonObjectCreationHandling? _objectCreationHandling;
internal JsonObjectCreationHandling EffectiveObjectCreationHandling { get; private set; }
// Because the getter can initialize its own backing field, we want to avoid races between the getter and setter.
// This is done using CAS on the single _attributeProvider field which employs the following encoding:
// null: not initialized, s_nullAttributeProvider: null, otherwise: _attributeProvider
private ICustomAttributeProvider? _attributeProvider;
private static readonly ICustomAttributeProvider s_nullAttributeProvider = typeof(NullAttributeProviderPlaceholder);
Comment thread
eiriktsarpalis marked this conversation as resolved.
private sealed class NullAttributeProviderPlaceholder;

[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "Looks up members that are already being referenced by the source generator.")]
private ICustomAttributeProvider InitializeAttributeProvider()
{
// If the property is source generated, perform a reflection lookup of its MemberInfo.
// Avoids overhead of reflection at startup and makes this method trimmable if unused.
ICustomAttributeProvider? provider = IsSourceGenerated && MemberName != null
? DefaultJsonTypeInfoResolver.LookupMemberInfo(DeclaringType, MemberType, MemberName)
: null;

provider ??= s_nullAttributeProvider;
return Interlocked.CompareExchange(ref _attributeProvider, provider, null) ?? provider;
}

/// <summary>
/// Gets or sets a value indicating if the property or field should be replaced or populated during deserialization.
Expand Down Expand Up @@ -202,10 +225,13 @@ public JsonObjectCreationHandling? ObjectCreationHandling
}
}

private ICustomAttributeProvider? _attributeProvider;
private JsonObjectCreationHandling? _objectCreationHandling;
internal JsonObjectCreationHandling EffectiveObjectCreationHandling { get; private set; }

internal string? MemberName { get; set; }
internal MemberTypes MemberType { get; set; }
internal bool IsVirtual { get; set; }
internal bool IsSourceGenerated { get; set; }

/// <summary>
/// Specifies whether the current property is a special extension data property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public interface ITestContext
public JsonTypeInfo<TypeWithDerivedAttribute> TypeWithDerivedAttribute { get; }
public JsonTypeInfo<PolymorphicClass> PolymorphicClass { get; }
public JsonTypeInfo<PocoWithNumberHandlingAttr> PocoWithNumberHandlingAttr { get; }
public JsonTypeInfo<PocoWithMixedVisibilityMembers> PocoWithMixedVisibilityMembers { get; }
}

internal partial class JsonContext : JsonSerializerContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(TypeWithDerivedAttribute))]
[JsonSerializable(typeof(PolymorphicClass))]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr))]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers))]
internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers), GenerationMode = JsonSourceGenerationMode.Metadata)]
internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
Expand Down Expand Up @@ -156,6 +157,7 @@ public override void EnsureFastPathGeneratedAsExpected()
[JsonSerializable(typeof(TypeWithDerivedAttribute))]
[JsonSerializable(typeof(PolymorphicClass))]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr))]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers))]
internal partial class MetadataContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
internal partial class MixedModeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Xunit;
Expand Down Expand Up @@ -1111,5 +1112,36 @@ public void NumberHandlingHonoredOnPoco()
JsonTestHelper.AssertJsonEqual(@"{""Id"":""0""}", JsonSerializer.Serialize(new PocoWithNumberHandlingAttr(), DefaultContext.PocoWithNumberHandlingAttr));
}
}

[Theory]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.PublicProperty))]
[InlineData(MemberTypes.Field, nameof(PocoWithMixedVisibilityMembers.PublicField))]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.InternalProperty))]
[InlineData(MemberTypes.Field, nameof(PocoWithMixedVisibilityMembers.InternalField))]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.PropertyWithCustomName), "customProp")]
[InlineData(MemberTypes.Field, nameof(PocoWithMixedVisibilityMembers.FieldWithCustomName), "customField")]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.BaseProperty))]
[InlineData(MemberTypes.Property, nameof(PocoWithMixedVisibilityMembers.ShadowProperty))]
public void JsonPropertyInfo_PopulatesAttributeProvider(MemberTypes memberType, string propertyName, string? jsonPropertyName = null)
{
if (DefaultContext.JsonSourceGenerationMode is JsonSourceGenerationMode.Serialization)
{
return; // No metadata generated
}

JsonTypeInfo typeInfo = DefaultContext.PocoWithMixedVisibilityMembers;
string name = jsonPropertyName ?? propertyName;
JsonPropertyInfo prop = typeInfo.Properties.FirstOrDefault(prop => prop.Name == name);
Assert.NotNull(prop);

MemberInfo memberInfo = Assert.IsAssignableFrom<MemberInfo>(prop.AttributeProvider);
string? actualJsonPropertyName = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name;

Assert.True(memberInfo.DeclaringType.IsAssignableFrom(typeInfo.Type));
Assert.Equal(memberType, memberInfo.MemberType);
Assert.Equal(prop.PropertyType, memberInfo is PropertyInfo p ? p.PropertyType : ((FieldInfo)memberInfo).FieldType);
Assert.Equal(propertyName, memberInfo.Name);
Assert.Equal(jsonPropertyName, actualJsonPropertyName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(TypeWithDerivedAttribute))]
[JsonSerializable(typeof(PolymorphicClass))]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr))]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers))]
internal partial class SerializationContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
Expand Down Expand Up @@ -109,6 +110,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
Expand Down Expand Up @@ -162,6 +164,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
[JsonSerializable(typeof(TypeWithDerivedAttribute), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PolymorphicClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PocoWithNumberHandlingAttr), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PocoWithMixedVisibilityMembers), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,32 @@ public class PocoWithNumberHandlingAttr
{
public int Id { get; set; }
}

public class PocoWithMixedVisibilityMembersBase
{
public string BaseProperty { get; set; }
public string ShadowProperty { get; set; }
}

public class PocoWithMixedVisibilityMembers : PocoWithMixedVisibilityMembersBase
{
public string PublicProperty { get; set; }

[JsonInclude]
public string PublicField;

[JsonInclude]
internal int InternalProperty { get; set; }

[JsonInclude]
internal int InternalField;

[JsonPropertyName("customProp")]
public string PropertyWithCustomName { get; set; }

[JsonInclude, JsonPropertyName("customField")]
public string FieldWithCustomName;

public new int ShadowProperty { get; set; }
}
}