Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JsonIncludeAttribute & support for non-public accessors #34675

Merged
merged 4 commits into from
Apr 14, 2020
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
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