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 bcc2ffec78ceed..b238ee41142e15 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -218,6 +218,8 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults defaults) {
public System.Text.Encodings.Web.JavaScriptEncoder? Encoder { get { throw null; } set { } }
public bool IgnoreNullValues { get { throw null; } set { } }
public bool IgnoreReadOnlyProperties { get { throw null; } set { } }
+ public bool IgnoreReadOnlyFields { get { throw null; } set { } }
+ public bool IncludeFields { get { throw null; } set { } }
public int MaxDepth { get { throw null; } set { } }
public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
public System.Text.Json.JsonNamingPolicy? PropertyNamingPolicy { get { throw null; } set { } }
@@ -479,7 +481,7 @@ public abstract partial class JsonConverter
internal JsonConverter() { }
public abstract bool CanConvert(System.Type typeToConvert);
}
- [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Enum | System.AttributeTargets.Property | System.AttributeTargets.Struct, AllowMultiple=false)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Enum | System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple=false)]
public partial class JsonConverterAttribute : System.Text.Json.Serialization.JsonAttribute
{
protected JsonConverterAttribute() { }
@@ -506,23 +508,23 @@ public sealed partial class JsonConstructorAttribute : System.Text.Json.Serializ
{
public JsonConstructorAttribute() { }
}
- [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple=false)]
public sealed partial class JsonExtensionDataAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonExtensionDataAttribute() { }
}
- [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple=false)]
public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonIgnoreAttribute() { }
public System.Text.Json.Serialization.JsonIgnoreCondition Condition { get { throw null; } set { } }
}
- [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple = false)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public sealed partial class JsonIncludeAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonIncludeAttribute() { }
}
- [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)]
+ [System.AttributeUsageAttribute(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple=false)]
public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonPropertyNameAttribute(string name) { }
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs
index 51095cd5c7b125..252bebc03d5930 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonConverterAttribute.cs
@@ -5,16 +5,16 @@
namespace System.Text.Json.Serialization
{
///
- /// When placed on a property or type, specifies the converter type to use.
+ /// When placed on a property, field, or type, specifies the converter type to use.
///
///
/// The specified converter type must derive from .
- /// When placed on a property, the specified converter will always be used.
+ /// When placed on a property or field, the specified converter will always be used.
/// When placed on a type, the specified converter will be used unless a compatible converter is added to
- /// or there is another on a property
+ /// or there is another on a member
/// of the same type.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = false)]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class JsonConverterAttribute : JsonAttribute
{
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonExtensionDataAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonExtensionDataAttribute.cs
index 58013febe3a175..8b53e1ccbed8ad 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonExtensionDataAttribute.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonExtensionDataAttribute.cs
@@ -5,8 +5,8 @@
namespace System.Text.Json.Serialization
{
///
- /// When placed on a property of type , any
- /// properties that do not have a matching member are added to that Dictionary during deserialization and written during serialization.
+ /// When placed on a property or field of type , any
+ /// properties that do not have a matching property or field are added to that Dictionary during deserialization and written during serialization.
///
///
/// The TKey value must be and TValue must be or .
@@ -14,13 +14,13 @@ namespace System.Text.Json.Serialization
/// During deserializing, when using a "null" JSON value is treated as a null object reference, and when using
/// a "null" is treated as a JsonElement with set to .
///
- /// During serializing, the name of the extension data property is not included in the JSON;
+ /// During serializing, the name of the extension data member is not included in the JSON;
/// the data contained within the extension data is serialized as properties of the JSON object.
///
- /// If there is more than one extension property on a type, or it the property is not of the correct type,
+ /// If there is more than one extension member on a type, or it the member is not of the correct type,
/// an is thrown during the first serialization or deserialization of that type.
///
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonExtensionDataAttribute : JsonAttribute
{
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIgnoreAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIgnoreAttribute.cs
index 2b081d4b4b5b79..2c3f18cb255110 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIgnoreAttribute.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIgnoreAttribute.cs
@@ -5,13 +5,13 @@
namespace System.Text.Json.Serialization
{
///
- /// Prevents a property from being serialized or deserialized.
+ /// Prevents a property or field from being serialized or deserialized.
///
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonIgnoreAttribute : JsonAttribute
{
///
- /// Specifies the condition that must be met before a property will be ignored.
+ /// Specifies the condition that must be met before a property or field will be ignored.
///
/// The default value is .
public JsonIgnoreCondition Condition { get; set; } = JsonIgnoreCondition.Always;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIncludeAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIncludeAttribute.cs
index 0e1727d683a56e..2f868dc5c3dd1b 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIncludeAttribute.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonIncludeAttribute.cs
@@ -10,7 +10,7 @@ namespace System.Text.Json.Serialization
///
/// When applied to a property, indicates that non-public getters and setters can be used for serialization and deserialization.
///
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ [AttributeUsage(AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonIncludeAttribute : JsonAttribute
{
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyNameAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyNameAttribute.cs
index 563ed5d6fd3904..250ccb31b1930e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyNameAttribute.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyNameAttribute.cs
@@ -8,7 +8,7 @@ namespace System.Text.Json.Serialization
/// Specifies the property name that is present in the JSON when serializing and deserializing.
/// This overrides any naming policy specified by .
///
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonPropertyNameAttribute : JsonAttribute
{
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs
index 5ef01ee542b1d8..2a2e69691b77fd 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.Cache.cs
@@ -53,28 +53,26 @@ internal sealed partial class JsonClassInfo
// Use an array (instead of List) for highest performance.
private volatile PropertyRef[]? _propertyRefsSorted;
- public static JsonPropertyInfo AddProperty(PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options)
+ public static JsonPropertyInfo AddProperty(MemberInfo memberInfo, Type memberType, Type parentClassType, JsonSerializerOptions options)
{
- JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute(propertyInfo)?.Condition;
+ JsonIgnoreCondition? ignoreCondition = JsonPropertyInfo.GetAttribute(memberInfo)?.Condition;
if (ignoreCondition == JsonIgnoreCondition.Always)
{
- return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(propertyInfo, options);
+ return JsonPropertyInfo.CreateIgnoredPropertyPlaceholder(memberInfo, options);
}
- Type propertyType = propertyInfo.PropertyType;
-
JsonConverter converter = GetConverter(
- propertyType,
+ memberType,
parentClassType,
- propertyInfo,
+ memberInfo,
out Type runtimeType,
options);
return CreateProperty(
- declaredPropertyType: propertyType,
+ declaredPropertyType: memberType,
runtimePropertyType: runtimeType,
- propertyInfo,
+ memberInfo,
parentClassType,
converter,
options,
@@ -84,7 +82,7 @@ public static JsonPropertyInfo AddProperty(PropertyInfo propertyInfo, Type paren
internal static JsonPropertyInfo CreateProperty(
Type declaredPropertyType,
Type? runtimePropertyType,
- PropertyInfo? propertyInfo,
+ MemberInfo? memberInfo,
Type parentClassType,
JsonConverter converter,
JsonSerializerOptions options,
@@ -98,7 +96,7 @@ internal static JsonPropertyInfo CreateProperty(
declaredPropertyType,
runtimePropertyType,
runtimeClassType: converter.ClassType,
- propertyInfo,
+ memberInfo,
converter,
ignoreCondition,
options);
@@ -119,7 +117,7 @@ internal static JsonPropertyInfo CreatePropertyInfoForClassInfo(
JsonPropertyInfo jsonPropertyInfo = CreateProperty(
declaredPropertyType: declaredPropertyType,
runtimePropertyType: runtimePropertyType,
- propertyInfo: null, // Not a real property so this is null.
+ memberInfo: null, // Not a real property so this is null.
parentClassType: JsonClassInfo.ObjectType, // a dummy value (not used)
converter: converter,
options);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
index 22bc9e32bdaf7e..8a7d8e16eefb5d 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace System.Text.Json
@@ -84,7 +85,7 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
JsonConverter converter = GetConverter(
Type,
parentClassType: null, // A ClassInfo never has a "parent" class.
- propertyInfo: null, // A ClassInfo never has a "parent" property.
+ memberInfo: null, // A ClassInfo never has a "parent" property.
out Type runtimeType,
Options);
@@ -95,21 +96,27 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
{
case ClassType.Object:
{
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+ CreateObject = Options.MemberAccessorStrategy.CreateConstructor(type);
Dictionary cache = new Dictionary(
Options.PropertyNameCaseInsensitive
? StringComparer.OrdinalIgnoreCase
: StringComparer.Ordinal);
- Dictionary? ignoredProperties = null;
+ Dictionary? ignoredMembers = null;
// We start from the most derived type.
for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
{
- foreach (PropertyInfo propertyInfo in currentType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
+ const BindingFlags bindingFlags =
+ BindingFlags.Instance |
+ BindingFlags.Public |
+ BindingFlags.NonPublic |
+ BindingFlags.DeclaredOnly;
+
+ foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
{
// Ignore indexers and virtual properties that have overrides that were [JsonIgnore]d.
- if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, ignoredProperties))
+ if (propertyInfo.GetIndexParameters().Length > 0 || PropertyIsOverridenAndIgnored(propertyInfo, ignoredMembers))
{
continue;
}
@@ -118,52 +125,43 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
if (propertyInfo.GetMethod?.IsPublic == true ||
propertyInfo.SetMethod?.IsPublic == true)
{
- JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo, currentType, options);
- Debug.Assert(jsonPropertyInfo != null && jsonPropertyInfo.NameAsString != null);
-
- string propertyName = propertyInfo.Name;
-
- // The JsonPropertyNameAttribute or naming policy resulted in a collision.
- if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
+ CacheMember(currentType, propertyInfo.PropertyType, propertyInfo, cache, ref ignoredMembers);
+ }
+ else
+ {
+ if (JsonPropertyInfo.GetAttribute(propertyInfo) != null)
{
- JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];
-
- if (other.IsIgnored)
- {
- // Overwrite previously cached property since it has [JsonIgnore].
- cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
- }
- else if (
- // Does the current property have `JsonIgnoreAttribute`?
- !jsonPropertyInfo.IsIgnored &&
- // Is the current property hidden by the previously cached property
- // (with `new` keyword, or by overriding)?
- other.PropertyInfo!.Name != propertyName &&
- // Was a property with the same CLR name ignored? That property hid the current property,
- // thus, if it was ignored, the current property should be ignored too.
- ignoredProperties?.ContainsKey(propertyName) != true
- )
- {
- // Throw if we have two public properties with the same JSON property name,
- // neither overrides or hides the other, and neither have been ignored.
- ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo);
- }
- // Ignore the current property.
+ ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType);
}
- if (jsonPropertyInfo.IsIgnored)
+ // Non-public properties should not be included for (de)serialization.
+ }
+ }
+
+ foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags))
+ {
+ if (PropertyIsOverridenAndIgnored(fieldInfo, ignoredMembers))
+ {
+ continue;
+ }
+
+ bool hasJsonInclude = JsonPropertyInfo.GetAttribute(fieldInfo) != null;
+
+ if (fieldInfo.IsPublic)
+ {
+ if (hasJsonInclude || Options.IncludeFields)
{
- (ignoredProperties ??= new Dictionary())[propertyName] = propertyInfo;
+ CacheMember(currentType, fieldInfo.FieldType, fieldInfo, cache, ref ignoredMembers);
}
}
else
{
- if (JsonPropertyInfo.GetAttribute(propertyInfo) != null)
+ if (hasJsonInclude)
{
- ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(propertyInfo, currentType);
+ ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(fieldInfo, currentType);
}
- // Non-public properties should not be included for (de)serialization.
+ // Non-public fields should not be included for (de)serialization.
}
}
}
@@ -207,13 +205,13 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
case ClassType.Dictionary:
{
ElementType = converter.ElementType;
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(runtimeType);
+ CreateObject = Options.MemberAccessorStrategy.CreateConstructor(runtimeType);
}
break;
case ClassType.Value:
case ClassType.NewValue:
{
- CreateObject = options.MemberAccessorStrategy.CreateConstructor(type);
+ CreateObject = Options.MemberAccessorStrategy.CreateConstructor(type);
}
break;
case ClassType.None:
@@ -227,6 +225,50 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
}
}
+ private void CacheMember(
+ Type declaringType,
+ Type memberType,
+ MemberInfo memberInfo,
+ Dictionary cache,
+ ref Dictionary? ignoredMembers)
+ {
+ JsonPropertyInfo jsonPropertyInfo = AddProperty(memberInfo, memberType, declaringType, Options);
+ Debug.Assert(jsonPropertyInfo.NameAsString != null);
+
+ string memberName = memberInfo.Name;
+
+ // The JsonPropertyNameAttribute or naming policy resulted in a collision.
+ if (!JsonHelpers.TryAdd(cache, jsonPropertyInfo.NameAsString, jsonPropertyInfo))
+ {
+ JsonPropertyInfo other = cache[jsonPropertyInfo.NameAsString];
+
+ if (other.IsIgnored)
+ {
+ // Overwrite previously cached property since it has [JsonIgnore].
+ cache[jsonPropertyInfo.NameAsString] = jsonPropertyInfo;
+ }
+ else if (
+ // Does the current property have `JsonIgnoreAttribute`?
+ !jsonPropertyInfo.IsIgnored &&
+ // Is the current property hidden by the previously cached property
+ // (with `new` keyword, or by overriding)?
+ other.MemberInfo!.Name != 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.
+ ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(Type, jsonPropertyInfo);
+ }
+ // Ignore the current property.
+ }
+
+ if (jsonPropertyInfo.IsIgnored)
+ {
+ (ignoredMembers ??= new Dictionary()).Add(memberName, memberInfo);
+ }
+ }
+
private void InitializeConstructorParameters(Dictionary propertyCache, ConstructorInfo constructorInfo)
{
ParameterInfo[] parameters = constructorInfo!.GetParameters();
@@ -235,19 +277,24 @@ private void InitializeConstructorParameters(Dictionary(memberInfo).FieldType;
- string camelCasePropName = JsonNamingPolicy.CamelCase.ConvertName(propertyInfo.Name);
+ string camelCasePropName = JsonNamingPolicy.CamelCase.ConvertName(memberInfo.Name);
if (parameterInfo.Name == camelCasePropName &&
- parameterInfo.ParameterType == propertyInfo.PropertyType)
+ parameterInfo.ParameterType == memberType)
{
if (isBound)
{
@@ -259,7 +306,7 @@ private void InitializeConstructorParameters(Dictionary? ignoredProperties)
+ private static bool PropertyIsOverridenAndIgnored(MemberInfo currentMember, Dictionary? ignoredMembers)
{
- if (ignoredProperties == null || !ignoredProperties.TryGetValue(currentProperty.Name, out PropertyInfo? ignoredProperty))
+ if (ignoredMembers == null || !ignoredMembers.TryGetValue(currentMember.Name, out MemberInfo? ignoredProperty))
{
return false;
}
- return currentProperty.PropertyType == ignoredProperty.PropertyType &&
- PropertyIsVirtual(currentProperty) &&
- PropertyIsVirtual(ignoredProperty);
+ Debug.Assert(currentMember is PropertyInfo || currentMember is FieldInfo);
+ PropertyInfo? currentPropertyInfo = currentMember as PropertyInfo;
+ Type currentMemberType = currentPropertyInfo == null
+ ? Unsafe.As(currentMember).FieldType
+ : currentPropertyInfo.PropertyType;
+
+ Debug.Assert(ignoredProperty is PropertyInfo || ignoredProperty is FieldInfo);
+ PropertyInfo? ignoredPropertyInfo = ignoredProperty as PropertyInfo;
+ Type ignoredPropertyType = ignoredPropertyInfo == null
+ ? Unsafe.As(ignoredProperty).FieldType
+ : ignoredPropertyInfo.PropertyType;
+
+ return currentMemberType == ignoredPropertyType &&
+ PropertyIsVirtual(currentPropertyInfo) &&
+ PropertyIsVirtual(ignoredPropertyInfo);
}
- private static bool PropertyIsVirtual(PropertyInfo propertyInfo)
+ private static bool PropertyIsVirtual(PropertyInfo? propertyInfo)
{
- return propertyInfo.GetMethod?.IsVirtual == true || propertyInfo.SetMethod?.IsVirtual == true;
+ return propertyInfo != null && (propertyInfo.GetMethod?.IsVirtual == true || propertyInfo.SetMethod?.IsVirtual == true);
}
public bool DetermineExtensionDataProperty(Dictionary cache)
@@ -338,8 +397,8 @@ public bool DetermineExtensionDataProperty(Dictionary
foreach (JsonPropertyInfo jsonPropertyInfo in cache.Values)
{
- Debug.Assert(jsonPropertyInfo.PropertyInfo != null);
- Attribute? attribute = jsonPropertyInfo.PropertyInfo.GetCustomAttribute(attributeType);
+ Debug.Assert(jsonPropertyInfo.MemberInfo != null);
+ Attribute? attribute = jsonPropertyInfo.MemberInfo.GetCustomAttribute(attributeType);
if (attribute != null)
{
if (property != null)
@@ -365,8 +424,8 @@ private static JsonParameterInfo AddConstructorParameter(
}
JsonConverter converter = jsonPropertyInfo.ConverterBase;
-
JsonParameterInfo jsonParameterInfo = converter.CreateJsonParameterInfo();
+
jsonParameterInfo.Initialize(
jsonPropertyInfo.RuntimePropertyType!,
parameterInfo,
@@ -385,14 +444,14 @@ private static JsonParameterInfo AddConstructorParameter(
public static JsonConverter GetConverter(
Type type,
Type? parentClassType,
- PropertyInfo? propertyInfo,
+ MemberInfo? memberInfo,
out Type runtimeType,
JsonSerializerOptions options)
{
Debug.Assert(type != null);
- ValidateType(type, parentClassType, propertyInfo, options);
+ ValidateType(type, parentClassType, memberInfo, options);
- JsonConverter converter = options.DetermineConverter(parentClassType, type, propertyInfo)!;
+ JsonConverter converter = options.DetermineConverter(parentClassType, type, memberInfo);
// The runtimeType is the actual value being assigned to the property.
// There are three types to consider for the runtimeType:
@@ -439,11 +498,11 @@ public static JsonConverter GetConverter(
return converter;
}
- private static void ValidateType(Type type, Type? parentClassType, PropertyInfo? propertyInfo, JsonSerializerOptions options)
+ private static void ValidateType(Type type, Type? parentClassType, MemberInfo? memberInfo, JsonSerializerOptions options)
{
if (!options.TypeIsCached(type) && IsInvalidForSerialization(type))
{
- ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(type, parentClassType, propertyInfo);
+ ThrowHelper.ThrowInvalidOperationException_CannotSerializeInvalidType(type, parentClassType, memberInfo);
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
index 471321029c82af..410d4778db7d82 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
@@ -10,7 +10,7 @@
namespace System.Text.Json
{
- [DebuggerDisplay("PropertyInfo={PropertyInfo}")]
+ [DebuggerDisplay("MemberInfo={MemberInfo}")]
internal abstract class JsonPropertyInfo
{
public static readonly JsonPropertyInfo s_missingProperty = GetPropertyPlaceholder();
@@ -36,11 +36,11 @@ public static JsonPropertyInfo GetPropertyPlaceholder()
// Create a property that is ignored at run-time. It uses the same type (typeof(sbyte)) to help
// prevent issues with unsupported types and helps ensure we don't accidently (de)serialize it.
- public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo propertyInfo, JsonSerializerOptions options)
+ public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(MemberInfo memberInfo, JsonSerializerOptions options)
{
JsonPropertyInfo jsonPropertyInfo = new JsonPropertyInfo();
jsonPropertyInfo.Options = options;
- jsonPropertyInfo.PropertyInfo = propertyInfo;
+ jsonPropertyInfo.MemberInfo = memberInfo;
jsonPropertyInfo.DeterminePropertyName();
jsonPropertyInfo.IsIgnored = true;
@@ -54,12 +54,12 @@ public static JsonPropertyInfo CreateIgnoredPropertyPlaceholder(PropertyInfo pro
private void DeterminePropertyName()
{
- if (PropertyInfo == null)
+ if (MemberInfo == null)
{
return;
}
- JsonPropertyNameAttribute? nameAttribute = GetAttribute(PropertyInfo);
+ JsonPropertyNameAttribute? nameAttribute = GetAttribute(MemberInfo);
if (nameAttribute != null)
{
string name = nameAttribute.Name;
@@ -72,7 +72,7 @@ private void DeterminePropertyName()
}
else if (Options.PropertyNamingPolicy != null)
{
- string name = Options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name);
+ string name = Options.PropertyNamingPolicy.ConvertName(MemberInfo.Name);
if (name == null)
{
ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(ParentClassType, this);
@@ -82,7 +82,7 @@ private void DeterminePropertyName()
}
else
{
- NameAsString = PropertyInfo.Name;
+ NameAsString = MemberInfo.Name;
}
Debug.Assert(NameAsString != null);
@@ -98,10 +98,12 @@ private void DetermineSerializationCapabilities(JsonIgnoreCondition? ignoreCondi
Debug.Assert(ignoreCondition != JsonIgnoreCondition.Always);
// Three possible values for ignoreCondition:
- // null = JsonIgnore was not placed on this property, global IgnoreReadOnlyProperties wins
- // WhenNull = only ignore when null, global IgnoreReadOnlyProperties loses
- // Never = never ignore (always include), global IgnoreReadOnlyProperties loses
- bool serializeReadOnlyProperty = ignoreCondition != null || !Options.IgnoreReadOnlyProperties;
+ // null = JsonIgnore was not placed on this property, global IgnoreReadOnlyProperties/Fields wins
+ // WhenNull = only ignore when null, global IgnoreReadOnlyProperties/Fields loses
+ // Never = never ignore (always include), global IgnoreReadOnlyProperties/Fields loses
+ bool serializeReadOnlyProperty = ignoreCondition != null || (MemberInfo is PropertyInfo
+ ? !Options.IgnoreReadOnlyProperties
+ : !Options.IgnoreReadOnlyFields);
// We serialize if there is a getter + not ignoring readonly properties.
ShouldSerialize = HasGetter && (HasSetter || serializeReadOnlyProperty);
@@ -129,7 +131,7 @@ private void DetermineIgnoreCondition(JsonIgnoreCondition? ignoreCondition)
{
if (ignoreCondition != null)
{
- Debug.Assert(PropertyInfo != null);
+ Debug.Assert(MemberInfo != null);
Debug.Assert(ignoreCondition != JsonIgnoreCondition.Always);
if (ignoreCondition != JsonIgnoreCondition.Never)
@@ -153,9 +155,9 @@ private void DetermineIgnoreCondition(JsonIgnoreCondition? ignoreCondition)
#pragma warning restore CS0618 // IgnoreNullValues is obsolete
}
- public static TAttribute? GetAttribute(PropertyInfo propertyInfo) where TAttribute : Attribute
+ public static TAttribute? GetAttribute(MemberInfo memberInfo) where TAttribute : Attribute
{
- return (TAttribute?)propertyInfo.GetCustomAttribute(typeof(TAttribute), inherit: false);
+ return (TAttribute?)memberInfo.GetCustomAttribute(typeof(TAttribute), inherit: false);
}
public abstract bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf8JsonWriter writer);
@@ -178,7 +180,7 @@ public virtual void Initialize(
Type declaredPropertyType,
Type? runtimePropertyType,
ClassType runtimeClassType,
- PropertyInfo? propertyInfo,
+ MemberInfo? memberInfo,
JsonConverter converter,
JsonIgnoreCondition? ignoreCondition,
JsonSerializerOptions options)
@@ -189,7 +191,7 @@ public virtual void Initialize(
DeclaredPropertyType = declaredPropertyType;
RuntimePropertyType = runtimePropertyType;
ClassType = runtimeClassType;
- PropertyInfo = propertyInfo;
+ MemberInfo = memberInfo;
ConverterBase = converter;
Options = options;
}
@@ -301,7 +303,7 @@ public bool ReadJsonExtensionDataValue(ref ReadStack state, ref Utf8JsonReader r
public Type ParentClassType { get; private set; } = null!;
- public PropertyInfo? PropertyInfo { get; private set; }
+ public MemberInfo? MemberInfo { get; private set; }
public JsonClassInfo RuntimeClassInfo
{
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs
index 159f446048e737..a7b3e65e785840 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs
@@ -26,7 +26,7 @@ public override void Initialize(
Type declaredPropertyType,
Type? runtimePropertyType,
ClassType runtimeClassType,
- PropertyInfo? propertyInfo,
+ MemberInfo? memberInfo,
JsonConverter converter,
JsonIgnoreCondition? ignoreCondition,
JsonSerializerOptions options)
@@ -36,34 +36,58 @@ public override void Initialize(
declaredPropertyType,
runtimePropertyType,
runtimeClassType,
- propertyInfo,
+ memberInfo,
converter,
ignoreCondition,
options);
- if (propertyInfo != null)
+ switch (memberInfo)
{
- bool useNonPublicAccessors = GetAttribute(propertyInfo) != null;
+ case PropertyInfo propertyInfo:
+ {
+ bool useNonPublicAccessors = GetAttribute(propertyInfo) != null;
+
+ MethodInfo? getMethod = propertyInfo.GetMethod;
+ if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors))
+ {
+ HasGetter = true;
+ Get = options.MemberAccessorStrategy.CreatePropertyGetter(propertyInfo);
+ }
+
+ MethodInfo? setMethod = propertyInfo.SetMethod;
+ if (setMethod != null && (setMethod.IsPublic || useNonPublicAccessors))
+ {
+ HasSetter = true;
+ Set = options.MemberAccessorStrategy.CreatePropertySetter(propertyInfo);
+ }
+
+ break;
+ }
- MethodInfo? getMethod = propertyInfo.GetMethod;
- if (getMethod != null && (getMethod.IsPublic || useNonPublicAccessors))
- {
- HasGetter = true;
- Get = options.MemberAccessorStrategy.CreatePropertyGetter(propertyInfo);
- }
+ case FieldInfo fieldInfo:
+ {
+ Debug.Assert(fieldInfo.IsPublic);
- MethodInfo? setMethod = propertyInfo.SetMethod;
- if (setMethod != null && (setMethod.IsPublic || useNonPublicAccessors))
- {
- HasSetter = true;
- Set = options.MemberAccessorStrategy.CreatePropertySetter(propertyInfo);
- }
- }
- else
- {
- IsForClassInfo = true;
- HasGetter = true;
- HasSetter = true;
+ HasGetter = true;
+ Get = options.MemberAccessorStrategy.CreateFieldGetter(fieldInfo);
+
+ if (!fieldInfo.IsInitOnly)
+ {
+ HasSetter = true;
+ Set = options.MemberAccessorStrategy.CreateFieldSetter(fieldInfo);
+ }
+
+ break;
+ }
+
+ default:
+ {
+ IsForClassInfo = true;
+ HasGetter = true;
+ HasSetter = true;
+
+ break;
+ }
}
GetPolicies(ignoreCondition);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
index 1d757ef80ce6b5..96195cc234f88b 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
@@ -82,21 +82,21 @@ void Add(JsonConverter converter) =>
///
public IList Converters { get; }
- internal JsonConverter DetermineConverter(Type? parentClassType, Type runtimePropertyType, PropertyInfo? propertyInfo)
+ internal JsonConverter DetermineConverter(Type? parentClassType, Type runtimePropertyType, MemberInfo? memberInfo)
{
JsonConverter converter = null!;
// Priority 1: attempt to get converter from JsonConverterAttribute on property.
- if (propertyInfo != null)
+ if (memberInfo != null)
{
Debug.Assert(parentClassType != null);
JsonConverterAttribute? converterAttribute = (JsonConverterAttribute?)
- GetAttributeThatCanHaveMultiple(parentClassType!, typeof(JsonConverterAttribute), propertyInfo);
+ GetAttributeThatCanHaveMultiple(parentClassType!, typeof(JsonConverterAttribute), memberInfo);
if (converterAttribute != null)
{
- converter = GetConverterFromAttribute(converterAttribute, typeToConvert: runtimePropertyType, classTypeAttributeIsOn: parentClassType!, propertyInfo);
+ converter = GetConverterFromAttribute(converterAttribute, typeToConvert: runtimePropertyType, classTypeAttributeIsOn: parentClassType!, memberInfo);
}
}
@@ -158,7 +158,7 @@ public JsonConverter GetConverter(Type typeToConvert)
if (converterAttribute != null)
{
- converter = GetConverterFromAttribute(converterAttribute, typeToConvert: typeToConvert, classTypeAttributeIsOn: typeToConvert, propertyInfo: null);
+ converter = GetConverterFromAttribute(converterAttribute, typeToConvert: typeToConvert, classTypeAttributeIsOn: typeToConvert, memberInfo: null);
}
}
@@ -215,7 +215,7 @@ public JsonConverter GetConverter(Type typeToConvert)
return converter;
}
- private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converterAttribute, Type typeToConvert, Type classTypeAttributeIsOn, PropertyInfo? propertyInfo)
+ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converterAttribute, Type typeToConvert, Type classTypeAttributeIsOn, MemberInfo? memberInfo)
{
JsonConverter? converter;
@@ -226,7 +226,7 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter
converter = converterAttribute.CreateConverter(typeToConvert);
if (converter == null)
{
- ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(classTypeAttributeIsOn, propertyInfo, typeToConvert);
+ ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(classTypeAttributeIsOn, memberInfo, typeToConvert);
}
}
else
@@ -234,7 +234,7 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter
ConstructorInfo? ctor = type.GetConstructor(Type.EmptyTypes);
if (!typeof(JsonConverter).IsAssignableFrom(type) || ctor == null || !ctor.IsPublic)
{
- ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(classTypeAttributeIsOn, propertyInfo);
+ ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(classTypeAttributeIsOn, memberInfo);
}
converter = (JsonConverter)Activator.CreateInstance(type)!;
@@ -250,16 +250,16 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter
return NullableConverterFactory.CreateValueConverter(underlyingType, converter);
}
- ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(classTypeAttributeIsOn, propertyInfo, typeToConvert);
+ ThrowHelper.ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(classTypeAttributeIsOn, memberInfo, typeToConvert);
}
return converter;
}
- private static Attribute? GetAttributeThatCanHaveMultiple(Type classType, Type attributeType, PropertyInfo propertyInfo)
+ private static Attribute? GetAttributeThatCanHaveMultiple(Type classType, Type attributeType, MemberInfo memberInfo)
{
- object[] attributes = propertyInfo.GetCustomAttributes(attributeType, inherit: false);
- return GetAttributeThatCanHaveMultiple(attributeType, classType, propertyInfo, attributes);
+ object[] attributes = memberInfo.GetCustomAttributes(attributeType, inherit: false);
+ return GetAttributeThatCanHaveMultiple(attributeType, classType, memberInfo, attributes);
}
private static Attribute? GetAttributeThatCanHaveMultiple(Type classType, Type attributeType)
@@ -268,7 +268,7 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter
return GetAttributeThatCanHaveMultiple(attributeType, classType, null, attributes);
}
- private static Attribute? GetAttributeThatCanHaveMultiple(Type attributeType, Type classType, PropertyInfo? propertyInfo, object[] attributes)
+ private static Attribute? GetAttributeThatCanHaveMultiple(Type attributeType, Type classType, MemberInfo? memberInfo, object[] attributes)
{
if (attributes.Length == 0)
{
@@ -280,7 +280,7 @@ private JsonConverter GetConverterFromAttribute(JsonConverterAttribute converter
return (Attribute)attributes[0];
}
- ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateAttribute(attributeType, classType, propertyInfo);
+ ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateAttribute(attributeType, classType, memberInfo);
return default;
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
index 66666db8396286..0efc410a855ef4 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
@@ -41,6 +41,8 @@ public sealed partial class JsonSerializerOptions
private bool _haveTypesBeenCreated;
private bool _ignoreNullValues;
private bool _ignoreReadOnlyProperties;
+ private bool _ignoreReadonlyFields;
+ private bool _includeFields;
private bool _propertyNameCaseInsensitive;
private bool _writeIndented;
@@ -79,6 +81,8 @@ public JsonSerializerOptions(JsonSerializerOptions options)
_allowTrailingCommas = options._allowTrailingCommas;
_ignoreNullValues = options._ignoreNullValues;
_ignoreReadOnlyProperties = options._ignoreReadOnlyProperties;
+ _ignoreReadonlyFields = options._ignoreReadonlyFields;
+ _includeFields = options._includeFields;
_propertyNameCaseInsensitive = options._propertyNameCaseInsensitive;
_writeIndented = options._writeIndented;
@@ -284,6 +288,50 @@ public bool IgnoreReadOnlyProperties
}
}
+ ///
+ /// Determines whether read-only fields are ignored during serialization.
+ /// A property is read-only if it isn't marked with the readonly keyword.
+ /// The default value is false.
+ ///
+ ///
+ /// Read-only fields are not deserialized regardless of this setting.
+ ///
+ ///
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ ///
+ public bool IgnoreReadOnlyFields
+ {
+ get
+ {
+ return _ignoreReadonlyFields;
+ }
+ set
+ {
+ VerifyMutable();
+ _ignoreReadonlyFields = value;
+ }
+ }
+
+ ///
+ /// Determines whether fields are handled serialization and deserialization.
+ /// The default value is false.
+ ///
+ ///
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ ///
+ public bool IncludeFields
+ {
+ get
+ {
+ return _includeFields;
+ }
+ set
+ {
+ VerifyMutable();
+ _includeFields = value;
+ }
+ }
+
///
/// Gets or sets the maximum depth allowed when serializing or deserializing JSON, with the default (i.e. 0) indicating a max depth of 64.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs
index ae87d6feffc84e..38fb5e0d5cf8d6 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs
@@ -25,5 +25,9 @@ public abstract JsonClassInfo.ParameterizedConstructorDelegate CreatePropertyGetter(PropertyInfo propertyInfo);
public abstract Action