Skip to content

Commit

Permalink
Add JsonIncludeAttribute & support for non-public accessors (#34675)
Browse files Browse the repository at this point in the history
* Add JsonIncludeAttribute & support for non-public property accessors

* Address review feedback

* Remove extra reflection call for non-public properties
  • Loading branch information
layomia authored Apr 14, 2020
1 parent 90e0905 commit b3e791d
Show file tree
Hide file tree
Showing 16 changed files with 452 additions and 35 deletions.
5 changes: 5 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,11 @@ public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization
public JsonIgnoreAttribute() { }
public System.Text.Json.Serialization.JsonIgnoreCondition Condition { get { throw null; } set { } }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple = false)]
public sealed partial class JsonIncludeAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonIncludeAttribute() { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Serialization.JsonAttribute
{
Expand Down
5 changes: 4 additions & 1 deletion src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -512,4 +512,7 @@
<data name="SerializeTypeInstanceNotSupported" xml:space="preserve">
<value>Serialization and deserialization of 'System.Type' instances are not supported and should be avoided since they can lead to security issues.</value>
</data>
</root>
<data name="JsonIncludeOnNonPublicInvalid" xml:space="preserve">
<value>The non-public property '{0}' on type '{1}' is annotated with 'JsonIncludeAttribute' which is invalid.</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyInfoOfTTypeToConvert.cs" />
<Compile Include="System\Text\Json\Serialization\JsonPropertyNameAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonIncludeAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\JsonResumableConverterOfT.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleMetadata.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandlePropertyName.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public Dictionary<string, JsonParameterInfo> CreateParameterCache(int capacity,
}
}

public static JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
public static JsonPropertyInfo AddProperty(PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
{
JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute<JsonIgnoreAttribute>(propertyInfo)?.Condition;

Expand All @@ -86,6 +86,8 @@ public static JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo prope
return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
}

Type propertyType = propertyInfo.PropertyType;

JsonConverter converter = GetConverter(
propertyType,
parentClassType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,9 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
{
case ClassType.Object:
{
// Create the policy property.
PropertyInfoForClassInfo = CreatePropertyInfoForClassInfo(type, runtimeType, converter!, options);

CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);

PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

Dictionary<string, JsonPropertyInfo> cache = CreatePropertyCache(properties.Length);

Expand All @@ -112,11 +109,22 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
continue;
}

if (IsNonPublicProperty(propertyInfo))
{
if (JsonPropertyInfo.GetAttribute<JsonIncludeAttribute>(propertyInfo) != null)
{
ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, Type);
}

// Non-public properties should not be included for (de)serialization.
continue;
}

// For now we only support public getters\setters
if (propertyInfo.GetMethod?.IsPublic == true ||
propertyInfo.SetMethod?.IsPublic == true)
{
JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options);
JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, type, options);
Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null);

// If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
Expand Down Expand Up @@ -189,6 +197,13 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
}
}

private static bool IsNonPublicProperty(PropertyInfo propertyInfo)
{
MethodInfo? getMethod = propertyInfo.GetMethod;
MethodInfo? setMethod = propertyInfo.SetMethod;
return !((getMethod != null && getMethod.IsPublic) || (setMethod != null && setMethod.IsPublic));
}

private void InitializeConstructorParameters(ConstructorInfo constructorInfo)
{
ParameterInfo[] parameters = constructorInfo!.GetParameters();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Text.Json.Serialization
{
/// <summary>
/// Indicates that the member should be included for serialization and deserialization.
/// </summary>
/// <remarks>
/// When applied to a property, indicates that non-public getters and setters can be used for serialization and deserialization.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class JsonIncludeAttribute : JsonAttribute
{
/// <summary>
/// Initializes a new instance of <see cref="JsonIncludeAttribute"/>.
/// </summary>
public JsonIncludeAttribute() { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ public override void Initialize(

if (propertyInfo != null)
{
if (propertyInfo.GetMethod?.IsPublic == true)
bool useNonPublicAccessors = GetAttribute<JsonIncludeAttribute>(propertyInfo) != null;

MethodInfo? getMethod = propertyInfo.GetMethod;
if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors))
{
HasGetter = true;
Get = options.MemberAccessorStrategy.CreatePropertyGetter<TTypeToConvert>(propertyInfo);
}

if (propertyInfo.SetMethod?.IsPublic == true)
MethodInfo? setMethod = propertyInfo.SetMethod;
if (setMethod != null && (setMethod.IsPublic || useNonPublicAccessors))
{
HasSetter = true;
Set = options.MemberAccessorStrategy.CreatePropertySetter<TTypeToConvert>(propertyInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ internal static JsonPropertyInfo LookupProperty(
if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
{
JsonPropertyInfo? dataExtProperty = state.Current.JsonClassInfo.DataExtensionProperty;
if (dataExtProperty != null)
if (dataExtProperty != null && dataExtProperty.HasGetter && dataExtProperty.HasSetter)
{
state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public override Func<IEnumerable<KeyValuePair<string, TElement>>, TCollection> C

private static Delegate CreatePropertyGetter(PropertyInfo propertyInfo, Type classType, Type propertyType)
{
MethodInfo? realMethod = propertyInfo.GetGetMethod();
MethodInfo? realMethod = propertyInfo.GetMethod;
Type objectType = typeof(object);

Debug.Assert(realMethod != null);
Expand Down Expand Up @@ -268,7 +268,7 @@ private static Delegate CreatePropertyGetter(PropertyInfo propertyInfo, Type cla

private static Delegate CreatePropertySetter(PropertyInfo propertyInfo, Type classType, Type propertyType)
{
MethodInfo? realMethod = propertyInfo.GetSetMethod();
MethodInfo? realMethod = propertyInfo.SetMethod;
Type objectType = typeof(object);

Debug.Assert(realMethod != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public override Func<IEnumerable<KeyValuePair<string, TElement>>, TCollection> C

public override Func<object, TProperty> CreatePropertyGetter<TProperty>(PropertyInfo propertyInfo)
{
MethodInfo getMethodInfo = propertyInfo.GetGetMethod()!;
MethodInfo getMethodInfo = propertyInfo.GetMethod!;

return delegate (object obj)
{
Expand All @@ -155,7 +155,7 @@ public override Func<object, TProperty> CreatePropertyGetter<TProperty>(Property

public override Action<object, TProperty> CreatePropertySetter<TProperty>(PropertyInfo propertyInfo)
{
MethodInfo setMethodInfo = propertyInfo.GetSetMethod()!;
MethodInfo setMethodInfo = propertyInfo.SetMethod!;

return delegate (object obj, TProperty value)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorP
throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, propertyInfo, classType, constructorInfo));
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(PropertyInfo propertyInfo, Type parentType)
{
throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnNonPublicInvalid, propertyInfo.Name, parentType));
}

[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotHonored(
Expand Down
Loading

0 comments on commit b3e791d

Please sign in to comment.