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 CreatePropertySetter(PropertyInfo propertyInfo); + + public abstract Func CreateFieldGetter(FieldInfo fieldInfo); + + public abstract Action CreateFieldSetter(FieldInfo fieldInfo); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index e5cd65d906a6cc..d897cf694dfed3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -221,33 +221,29 @@ private static DynamicMethod CreateImmutableDictionaryCreateRangeDelegate(Type c } public override Func CreatePropertyGetter(PropertyInfo propertyInfo) => - CreateDelegate>(CreatePropertyGetter(propertyInfo, propertyInfo.DeclaringType!, typeof(TProperty))); + CreateDelegate>(CreatePropertyGetter(propertyInfo, typeof(TProperty))); - private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Type classType, Type propertyType) + private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Type propertyType) { MethodInfo? realMethod = propertyInfo.GetMethod; - Type objectType = JsonClassInfo.ObjectType; - Debug.Assert(realMethod != null); - var dynamicMethod = new DynamicMethod( - realMethod.Name, - propertyType, - new[] { objectType }, - typeof(ReflectionEmitMemberAccessor).Module, - skipVisibility: true); + Type? declaringType = propertyInfo.DeclaringType; + Debug.Assert(declaringType != null); + + DynamicMethod dynamicMethod = CreateGetterMethod(propertyInfo.Name, propertyType); ILGenerator generator = dynamicMethod.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); - if (classType.IsValueType) + if (declaringType.IsValueType) { - generator.Emit(OpCodes.Unbox, classType); + generator.Emit(OpCodes.Unbox, declaringType); generator.Emit(OpCodes.Call, realMethod); } else { - generator.Emit(OpCodes.Castclass, classType); + generator.Emit(OpCodes.Castclass, declaringType); generator.Emit(OpCodes.Callvirt, realMethod); } @@ -257,34 +253,30 @@ private static DynamicMethod CreatePropertyGetter(PropertyInfo propertyInfo, Typ } public override Action CreatePropertySetter(PropertyInfo propertyInfo) => - CreateDelegate>(CreatePropertySetter(propertyInfo, propertyInfo.DeclaringType!, typeof(TProperty))); + CreateDelegate>(CreatePropertySetter(propertyInfo, typeof(TProperty))); - private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Type classType, Type propertyType) + private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Type propertyType) { MethodInfo? realMethod = propertyInfo.SetMethod; - Type objectType = JsonClassInfo.ObjectType; - Debug.Assert(realMethod != null); - var dynamicMethod = new DynamicMethod( - realMethod.Name, - typeof(void), - new[] { objectType, propertyType }, - typeof(ReflectionEmitMemberAccessor).Module, - skipVisibility: true); + Type? declaringType = propertyInfo.DeclaringType; + Debug.Assert(declaringType != null); + + DynamicMethod dynamicMethod = CreateSetterMethod(propertyInfo.Name, propertyType); ILGenerator generator = dynamicMethod.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); - if (classType.IsValueType) + if (declaringType.IsValueType) { - generator.Emit(OpCodes.Unbox, classType); + generator.Emit(OpCodes.Unbox, declaringType); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Call, realMethod); } else { - generator.Emit(OpCodes.Castclass, classType); + generator.Emit(OpCodes.Castclass, declaringType); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Callvirt, realMethod); }; @@ -294,6 +286,69 @@ private static DynamicMethod CreatePropertySetter(PropertyInfo propertyInfo, Typ return dynamicMethod; } + public override Func CreateFieldGetter(FieldInfo fieldInfo) => + CreateDelegate>(CreateFieldGetter(fieldInfo, typeof(TProperty))); + + private static DynamicMethod CreateFieldGetter(FieldInfo fieldInfo, Type fieldType) + { + Type? declaringType = fieldInfo.DeclaringType; + Debug.Assert(declaringType != null); + + DynamicMethod dynamicMethod = CreateGetterMethod(fieldInfo.Name, fieldType); + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit( + declaringType.IsValueType + ? OpCodes.Unbox + : OpCodes.Castclass, + declaringType); + generator.Emit(OpCodes.Ldfld, fieldInfo); + generator.Emit(OpCodes.Ret); + + return dynamicMethod; + } + + public override Action CreateFieldSetter(FieldInfo fieldInfo) => + CreateDelegate>(CreateFieldSetter(fieldInfo, typeof(TProperty))); + + private static DynamicMethod CreateFieldSetter(FieldInfo fieldInfo, Type fieldType) + { + Type? declaringType = fieldInfo.DeclaringType; + Debug.Assert(declaringType != null); + + DynamicMethod dynamicMethod = CreateSetterMethod(fieldInfo.Name, fieldType); + ILGenerator generator = dynamicMethod.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit( + declaringType.IsValueType + ? OpCodes.Unbox + : OpCodes.Castclass, + declaringType); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Stfld, fieldInfo); + generator.Emit(OpCodes.Ret); + + return dynamicMethod; + } + + private static DynamicMethod CreateGetterMethod(string memberName, Type memberType) => + new DynamicMethod( + memberName + "Getter", + memberType, + new[] { JsonClassInfo.ObjectType }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + private static DynamicMethod CreateSetterMethod(string memberName, Type memberType) => + new DynamicMethod( + memberName + "Setter", + typeof(void), + new[] { JsonClassInfo.ObjectType, memberType }, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + [return: NotNullIfNotNull("method")] private static T? CreateDelegate(DynamicMethod? method) where T : Delegate => (T?)method?.CreateDelegate(typeof(T)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index a56a708eb1a3f0..15fe1853956fa0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -157,5 +157,17 @@ public override Action CreatePropertySetter(Proper setMethodInfo.Invoke(obj, new object[] { value! }); }; } + + public override Func CreateFieldGetter(FieldInfo fieldInfo) => + delegate (object obj) + { + return (TProperty)fieldInfo.GetValue(obj)!; + }; + + public override Action CreateFieldSetter(FieldInfo fieldInfo) => + delegate (object obj, TProperty value) + { + fieldInfo.SetValue(obj, value); + }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 283dd60f78367f..c375b586ef40e8 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -204,7 +204,7 @@ public string PropertyPath() void AppendStackFrame(StringBuilder sb, in WriteStackFrame frame) { // Append the property name. - string? propertyName = frame.DeclaredJsonPropertyInfo?.PropertyInfo?.Name; + string? propertyName = frame.DeclaredJsonPropertyInfo?.MemberInfo?.Name; if (propertyName == null) { // Attempt to get the JSON property name from the property name specified in re-entry. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 71d3f2ada0f49a..ec120cafa1290a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -94,16 +94,16 @@ public static void ThrowJsonException(string? message = null) [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_CannotSerializeInvalidType(Type type, Type? parentClassType, PropertyInfo? propertyInfo) + public static void ThrowInvalidOperationException_CannotSerializeInvalidType(Type type, Type? parentClassType, MemberInfo? memberInfo) { if (parentClassType == null) { - Debug.Assert(propertyInfo == null); + Debug.Assert(memberInfo == null); throw new InvalidOperationException(SR.Format(SR.CannotSerializeInvalidType, type)); } - Debug.Assert(propertyInfo != null); - throw new InvalidOperationException(SR.Format(SR.CannotSerializeInvalidMember, type, propertyInfo.Name, parentClassType)); + Debug.Assert(memberInfo != null); + throw new InvalidOperationException(SR.Format(SR.CannotSerializeInvalidMember, type, memberInfo.Name, parentClassType)); } [DoesNotReturn] @@ -115,12 +115,12 @@ public static void ThrowInvalidOperationException_SerializationConverterNotCompa [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(Type classType, PropertyInfo? propertyInfo) + public static void ThrowInvalidOperationException_SerializationConverterOnAttributeInvalid(Type classType, MemberInfo? memberInfo) { string location = classType.ToString(); - if (propertyInfo != null) + if (memberInfo != null) { - location += $".{propertyInfo.Name}"; + location += $".{memberInfo.Name}"; } throw new InvalidOperationException(SR.Format(SR.SerializationConverterOnAttributeInvalid, location)); @@ -128,13 +128,13 @@ public static void ThrowInvalidOperationException_SerializationConverterOnAttrib [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(Type classTypeAttributeIsOn, PropertyInfo? propertyInfo, Type typeToConvert) + public static void ThrowInvalidOperationException_SerializationConverterOnAttributeNotCompatible(Type classTypeAttributeIsOn, MemberInfo? memberInfo, Type typeToConvert) { string location = classTypeAttributeIsOn.ToString(); - if (propertyInfo != null) + if (memberInfo != null) { - location += $".{propertyInfo.Name}"; + location += $".{memberInfo.Name}"; } throw new InvalidOperationException(SR.Format(SR.SerializationConverterOnAttributeNotCompatible, location, typeToConvert)); @@ -151,14 +151,14 @@ public static void ThrowInvalidOperationException_SerializerOptionsImmutable() [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidOperationException_SerializerPropertyNameConflict(Type type, JsonPropertyInfo jsonPropertyInfo) { - throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, type, jsonPropertyInfo.PropertyInfo?.Name)); + throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, type, jsonPropertyInfo.MemberInfo?.Name)); } [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidOperationException_SerializerPropertyNameNull(Type parentType, JsonPropertyInfo jsonPropertyInfo) { - throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameNull, parentType, jsonPropertyInfo.PropertyInfo?.Name)); + throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameNull, parentType, jsonPropertyInfo.MemberInfo?.Name)); } [DoesNotReturn] @@ -179,8 +179,8 @@ public static void ThrowInvalidOperationException_SerializerConverterFactoryRetu public static void ThrowInvalidOperationException_MultiplePropertiesBindToConstructorParameters( Type parentType, ParameterInfo parameterInfo, - PropertyInfo firstMatch, - PropertyInfo secondMatch, + MemberInfo firstMatch, + MemberInfo secondMatch, ConstructorInfo constructorInfo) { throw new InvalidOperationException( @@ -203,18 +203,18 @@ public static void ThrowInvalidOperationException_ConstructorParameterIncomplete [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam( - PropertyInfo propertyInfo, + MemberInfo memberInfo, Type classType, ConstructorInfo constructorInfo) { - throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, propertyInfo, classType, constructorInfo)); + throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, memberInfo, classType, constructorInfo)); } [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(PropertyInfo propertyInfo, Type parentType) + public static void ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(MemberInfo memberInfo, Type parentType) { - throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnNonPublicInvalid, propertyInfo.Name, parentType)); + throw new InvalidOperationException(SR.Format(SR.JsonIncludeOnNonPublicInvalid, memberInfo.Name, parentType)); } [DoesNotReturn] @@ -327,12 +327,12 @@ public static void AddJsonExceptionInformation(in WriteStack state, JsonExceptio [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidOperationException_SerializationDuplicateAttribute(Type attribute, Type classType, PropertyInfo? propertyInfo) + public static void ThrowInvalidOperationException_SerializationDuplicateAttribute(Type attribute, Type classType, MemberInfo? memberInfo) { string location = classType.ToString(); - if (propertyInfo != null) + if (memberInfo != null) { - location += $".{propertyInfo.Name}"; + location += $".{memberInfo.Name}"; } throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateAttribute, attribute, location)); @@ -356,7 +356,7 @@ public static void ThrowInvalidOperationException_SerializationDuplicateTypeAttr [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(Type type, JsonPropertyInfo jsonPropertyInfo) { - throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalid, type, jsonPropertyInfo.PropertyInfo?.Name)); + throw new InvalidOperationException(SR.Format(SR.SerializationDataExtensionPropertyInvalid, type, jsonPropertyInfo.MemberInfo?.Name)); } [DoesNotReturn] diff --git a/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs b/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs index d3f0808586f024..7d691794c8ce5c 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/CacheTests.cs @@ -163,7 +163,7 @@ public static void PropertyCacheWithMinInputsLast() // Use a common options instance to encourage additional metadata collisions across types. Also since // this options is not the default options instance the tests will not use previously cached metadata. - private static JsonSerializerOptions s_options = new JsonSerializerOptions(); + private static JsonSerializerOptions s_options = new JsonSerializerOptions { IncludeFields = true }; [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [MemberData(nameof(WriteSuccessCases))] @@ -174,7 +174,8 @@ public static void MultipleTypes(ITestClass testObj) // Get the test json with the default options to avoid cache pollution of Deserialize() below. testObj.Initialize(); testObj.Verify(); - string json = JsonSerializer.Serialize(testObj, type); + var options = new JsonSerializerOptions { IncludeFields = true }; + string json = JsonSerializer.Serialize(testObj, type, options); void Serialize() { diff --git a/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs b/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs index 8c95c471e509e4..52636be1f52b4a 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs @@ -55,6 +55,49 @@ void Verify() } } + [Fact] + public static void ExtensioFieldNotUsed() + { + string json = @"{""MyNestedClass"":" + SimpleTestClass.s_json + "}"; + ClassWithExtensionField obj = JsonSerializer.Deserialize(json); + Assert.Null(obj.MyOverflow); + } + + [Fact] + public static void ExtensionFieldRoundTrip() + { + ClassWithExtensionField obj; + + { + string json = @"{""MyIntMissing"":2, ""MyInt"":1, ""MyNestedClassMissing"":" + SimpleTestClass.s_json + "}"; + obj = JsonSerializer.Deserialize(json); + Verify(); + } + + // Round-trip the json. + { + string json = JsonSerializer.Serialize(obj); + obj = JsonSerializer.Deserialize(json); + Verify(); + + // The json should not contain the dictionary name. + Assert.DoesNotContain(nameof(ClassWithExtensionField.MyOverflow), json); + } + + void Verify() + { + Assert.NotNull(obj.MyOverflow); + Assert.Equal(1, obj.MyInt); + Assert.Equal(2, obj.MyOverflow["MyIntMissing"].GetInt32()); + + JsonProperty[] properties = obj.MyOverflow["MyNestedClassMissing"].EnumerateObject().ToArray(); + + // Verify a couple properties + Assert.Equal(1, properties.Where(prop => prop.Name == "MyInt16").First().Value.GetInt32()); + Assert.True(properties.Where(prop => prop.Name == "MyBooleanTrue").First().Value.GetBoolean()); + } + } + [Fact] public static void ExtensionPropertyIgnoredWhenWritingDefault() { diff --git a/src/libraries/System.Text.Json/tests/Serialization/Object.WriteTests.cs b/src/libraries/System.Text.Json/tests/Serialization/Object.WriteTests.cs index c099efaeaac2c7..7b281cb0c070e1 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Object.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Object.WriteTests.cs @@ -20,16 +20,17 @@ public static void VerifyTypeFail() [MemberData(nameof(WriteSuccessCases))] public static void Write(ITestClass testObj) { + var options = new JsonSerializerOptions { IncludeFields = true }; string json; { testObj.Initialize(); testObj.Verify(); - json = JsonSerializer.Serialize(testObj, testObj.GetType()); + json = JsonSerializer.Serialize(testObj, testObj.GetType(), options); } { - ITestClass obj = (ITestClass)JsonSerializer.Deserialize(json, testObj.GetType()); + ITestClass obj = (ITestClass)JsonSerializer.Deserialize(json, testObj.GetType(), options); obj.Verify(); } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs index 4385cd6fae5194..8684b5b763064c 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.NonPublicAccessors.cs @@ -296,6 +296,9 @@ private class ClassWithMixedPropertyAccessors_PropertyAttributes [InlineData(typeof(ClassWithPrivateProperty_WithJsonIncludeProperty))] [InlineData(typeof(ClassWithInternalProperty_WithJsonIncludeProperty))] [InlineData(typeof(ClassWithProtectedProperty_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithPrivateField_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithInternalField_WithJsonIncludeProperty))] + [InlineData(typeof(ClassWithProtectedField_WithJsonIncludeProperty))] public static void NonPublicProperty_WithJsonInclude_Invalid(Type type) { InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Deserialize("", type)); @@ -328,5 +331,25 @@ private class ClassWithProtectedProperty_WithJsonIncludeProperty [JsonInclude] protected string MyString { get; private set; } } + + private class ClassWithPrivateField_WithJsonIncludeProperty + { + [JsonInclude] + private string MyString = null; + + public override string ToString() => MyString; + } + + private class ClassWithInternalField_WithJsonIncludeProperty + { + [JsonInclude] + internal string MyString = null; + } + + private class ClassWithProtectedField_WithJsonIncludeProperty + { + [JsonInclude] + protected string MyString = null; + } } } diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs index c2fd2148b32840..9ba34fb40edf70 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs @@ -11,6 +11,23 @@ namespace System.Text.Json.Serialization.Tests { public static partial class PropertyVisibilityTests { + [Fact] + public static void Serialize_NewSlotPublicField() + { + // Serialize + var obj = new ClassWithNewSlotField(); + string json = JsonSerializer.Serialize(obj); + + Assert.Equal(@"{""MyString"":""NewDefaultValue""}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = JsonSerializer.Deserialize(json); + + Assert.Equal("NewValue", ((ClassWithNewSlotField)obj).MyString); + Assert.Equal("DefaultValue", ((ClassWithInternalField)obj).MyString); + } + [Fact] public static void Serialize_NewSlotPublicProperty() { @@ -103,6 +120,40 @@ public static void Serialize_NewSlotPublicProperty_ConflictWithBasePublicPropert Assert.Equal(2.5M, obj.MyNumeric); } + [Fact] + public static void Serialize_NewSlotPublicField_ConflictWithBasePublicProperty() + { + // Serialize + var obj = new ClassWithNewSlotDecimalField(); + string json = JsonSerializer.Serialize(obj); + + Assert.Equal(@"{""MyNumeric"":1.5}", json); + + // Deserialize + json = @"{""MyNumeric"":2.5}"; + obj = JsonSerializer.Deserialize(json); + + Assert.Equal(2.5M, obj.MyNumeric); + } + + [Fact] + public static void Serialize_NewSlotPublicField_SpecifiedJsonPropertyName() + { + // Serialize + var obj = new ClassWithNewSlotAttributedDecimalField(); + string json = JsonSerializer.Serialize(obj); + + Assert.Contains(@"""MyNewNumeric"":1.5", json); + Assert.Contains(@"""MyNumeric"":1", json); + + // Deserialize + json = @"{""MyNewNumeric"":2.5,""MyNumeric"":4}"; + obj = JsonSerializer.Deserialize(json); + + Assert.Equal(4, ((ClassWithHiddenByNewSlotIntProperty)obj).MyNumeric); + Assert.Equal(2.5M, ((ClassWithNewSlotAttributedDecimalField)obj).MyNumeric); + } + [Fact] public static void Serialize_NewSlotPublicProperty_SpecifiedJsonPropertyName() { @@ -137,6 +188,23 @@ public static void Ignore_NonPublicProperty() Assert.Equal("DefaultValue", obj.MyString); } + [Fact] + public static void Ignore_NewSlotPublicFieldIgnored() + { + // Serialize + var obj = new ClassWithIgnoredNewSlotField(); + string json = JsonSerializer.Serialize(obj); + + Assert.Equal(@"{}", json); + + // Deserialize + json = @"{""MyString"":""NewValue""}"; + obj = JsonSerializer.Deserialize(json); + + Assert.Equal("NewDefaultValue", ((ClassWithIgnoredNewSlotField)obj).MyString); + Assert.Equal("DefaultValue", ((ClassWithInternalField)obj).MyString); + } + [Fact] public static void Ignore_NewSlotPublicPropertyIgnored() { @@ -264,6 +332,20 @@ public static void Throw_PublicProperty_ConflictDueAttributes() () => JsonSerializer.Deserialize(json)); } + [Fact] + public static void Throw_PublicPropertyAndField_ConflictDueAttributes() + { + // Serialize + var obj = new ClassWithPropertyFieldNamingConflictWhichThrows(); + Assert.Throws( + () => JsonSerializer.Serialize(obj)); + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + Assert.Throws( + () => JsonSerializer.Deserialize(json)); + } + [Fact] public static void Throw_PublicProperty_ConflictDueAttributes_SingleInheritance() { @@ -287,6 +369,29 @@ public static void Throw_PublicProperty_ConflictDueAttributes_SingleInheritance( // obj.MyString still equals "DefaultValue" } + [Fact] + public static void Throw_PublicPropertyAndField_ConflictDueAttributes_SingleInheritance() + { + // Serialize + var obj = new ClassInheritedWithPropertyFieldNamingConflictWhichThrows(); + Assert.Throws( + () => JsonSerializer.Serialize(obj)); + + // The output for Newtonsoft.Json is: + // {"MyString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + Assert.Throws( + () => JsonSerializer.Deserialize(json)); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + [Fact] public static void Throw_PublicProperty_ConflictDueAttributes_DoubleInheritance() { @@ -311,6 +416,30 @@ public static void Throw_PublicProperty_ConflictDueAttributes_DoubleInheritance( // obj.MyString still equals "DefaultValue" } + [Fact] + public static void Throw_PublicPropertyAndField_ConflictDueAttributes_DoubleInheritance() + { + // Serialize + var obj = new ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows(); + Assert.Throws( + () => JsonSerializer.Serialize(obj)); + + // The output for Newtonsoft.Json is: + // {"MyString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + + Assert.Throws( + () => JsonSerializer.Deserialize(json)); + + // The output for Newtonsoft.Json is: + // obj.ConflictingString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + [Fact] public static void Throw_PublicProperty_ConflictDuePolicy() { @@ -327,6 +456,22 @@ public static void Throw_PublicProperty_ConflictDuePolicy() () => JsonSerializer.Deserialize(json, options)); } + [Fact] + public static void Throw_PublicPropertyAndField_ConflictDuePolicy() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassWithPropertyFieldPolicyConflictWhichThrows(); + Assert.Throws( + () => JsonSerializer.Serialize(obj, options)); + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + Assert.Throws( + () => JsonSerializer.Deserialize(json, options)); + } + [Fact] public static void Throw_PublicProperty_ConflictDuePolicy_SingleInheritance() { @@ -353,6 +498,32 @@ public static void Throw_PublicProperty_ConflictDuePolicy_SingleInheritance() // obj.MyString still equals "DefaultValue" } + [Fact] + public static void Throw_PublicPropertyAndField_ConflictDuePolicy_SingleInheritance() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassInheritedWithPropertyFieldPolicyConflictWhichThrows(); + + Assert.Throws( + () => JsonSerializer.Serialize(obj, options)); + + // The output for Newtonsoft.Json is: + // {"myString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + Assert.Throws( + () => JsonSerializer.Deserialize(json, options)); + + // The output for Newtonsoft.Json is: + // obj.myString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + [Fact] public static void Throw_PublicProperty_ConflictDuePolicy_DobuleInheritance() { @@ -380,6 +551,33 @@ public static void Throw_PublicProperty_ConflictDuePolicy_DobuleInheritance() // obj.MyString still equals "DefaultValue" } + [Fact] + public static void Throw_PublicPropertyAndField_ConflictDuePolicy_DobuleInheritance() + { + var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Serialize + var obj = new ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows(); + + Assert.Throws( + () => JsonSerializer.Serialize(obj, options)); + + // The output for Newtonsoft.Json is: + // {"myString":"ConflictingValue"} + // Conflicts at different type-hierarchy levels that are not caused by + // deriving or the new keyword are allowed. Properties on more derived types win. + + // Deserialize + string json = @"{""MyString"":""NewValue""}"; + + Assert.Throws( + () => JsonSerializer.Deserialize(json, options)); + + // The output for Newtonsoft.Json is: + // obj.myString = "NewValue" + // obj.MyString still equals "DefaultValue" + } + [Fact] public static void HiddenPropertiesIgnored_WhenOverridesIgnored() { @@ -430,6 +628,17 @@ public static void HiddenPropertiesIgnored_WhenOverridesIgnored() Assert.Equal(@"{""MyProp"":null}", serialized); } + public class ClassWithInternalField + { + internal string MyString = "DefaultValue"; + } + + public class ClassWithNewSlotField : ClassWithInternalField + { + [JsonInclude] + public new string MyString = "NewDefaultValue"; + } + public class ClassWithInternalProperty { internal string MyString { get; set; } = "DefaultValue"; @@ -466,12 +675,28 @@ public class ClassWithPropertyNamingConflictWhichThrows public string ConflictingString { get; set; } = "ConflictingValue"; } + public class ClassWithPropertyFieldNamingConflictWhichThrows + { + public string MyString { get; set; } = "DefaultValue"; + + [JsonInclude] + [JsonPropertyName(nameof(MyString))] + public string ConflictingString = "ConflictingValue"; + } + public class ClassInheritedWithPropertyNamingConflictWhichThrows : ClassWithPublicProperty { [JsonPropertyName(nameof(MyString))] public string ConflictingString { get; set; } = "ConflictingValue"; } + public class ClassInheritedWithPropertyFieldNamingConflictWhichThrows : ClassWithPublicProperty + { + [JsonInclude] + [JsonPropertyName(nameof(MyString))] + public string ConflictingString = "ConflictingValue"; + } + public class ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy : ClassWithPublicProperty { } @@ -482,6 +707,13 @@ public class ClassTwiceInheritedWithPropertyNamingConflictWhichThrows : ClassTwi public string ConflictingString { get; set; } = "ConflictingValue"; } + public class ClassTwiceInheritedWithPropertyFieldNamingConflictWhichThrows : ClassTwiceInheritedWithPropertyNamingConflictWhichThrowsDummy + { + [JsonInclude] + [JsonPropertyName(nameof(MyString))] + public string ConflictingString = "ConflictingValue"; + } + public class ClassWithPropertyPolicyConflict { public string MyString { get; set; } = "DefaultValue"; @@ -496,11 +728,25 @@ public class ClassWithPropertyPolicyConflictWhichThrows public string myString { get; set; } = "ConflictingValue"; } + public class ClassWithPropertyFieldPolicyConflictWhichThrows + { + public string MyString { get; set; } = "DefaultValue"; + + [JsonInclude] + public string myString = "ConflictingValue"; + } + public class ClassInheritedWithPropertyPolicyConflictWhichThrows : ClassWithPublicProperty { public string myString { get; set; } = "ConflictingValue"; } + public class ClassInheritedWithPropertyFieldPolicyConflictWhichThrows : ClassWithPublicProperty + { + [JsonInclude] + public string myString = "ConflictingValue"; + } + public class ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy : ClassWithPublicProperty { } @@ -510,6 +756,18 @@ public class ClassTwiceInheritedWithPropertyPolicyConflictWhichThrows : ClassInh public string myString { get; set; } = "ConflictingValue"; } + public class ClassTwiceInheritedWithPropertyFieldPolicyConflictWhichThrows : ClassInheritedWithPropertyPolicyConflictWhichThrowsDummy + { + [JsonInclude] + public string myString { get; set; } = "ConflictingValue"; + } + + public class ClassWithIgnoredNewSlotField : ClassWithInternalField + { + [JsonIgnore] + public new string MyString = "NewDefaultValue"; + } + public class ClassWithIgnoredNewSlotProperty : ClassWithInternalProperty { [JsonIgnore] @@ -566,11 +824,24 @@ public class ClassWithHiddenByNewSlotIntProperty public int MyNumeric { get; set; } = 1; } + public class ClassWithNewSlotDecimalField : ClassWithHiddenByNewSlotIntProperty + { + [JsonInclude] + public new decimal MyNumeric = 1.5M; + } + public class ClassWithNewSlotDecimalProperty : ClassWithHiddenByNewSlotIntProperty { public new decimal MyNumeric { get; set; } = 1.5M; } + public class ClassWithNewSlotAttributedDecimalField : ClassWithHiddenByNewSlotIntProperty + { + [JsonInclude] + [JsonPropertyName("MyNewNumeric")] + public new decimal MyNumeric = 1.5M; + } + public class ClassWithNewSlotAttributedDecimalProperty : ClassWithHiddenByNewSlotIntProperty { [JsonPropertyName("MyNewNumeric")] @@ -684,26 +955,27 @@ private class FurtherDerivedClass_With_IgnoredOverride : DerivedClass_WithConfli } [Fact] - public static void NoSetter() + public static void IgnoreReadOnlyProperties() { + var options = new JsonSerializerOptions(); + options.IgnoreReadOnlyProperties = true; + var obj = new ClassWithNoSetter(); - string json = JsonSerializer.Serialize(obj); - Assert.Contains(@"""MyString"":""DefaultValue""", json); - Assert.Contains(@"""MyInts"":[1,2]", json); + string json = JsonSerializer.Serialize(obj, options); - obj = JsonSerializer.Deserialize(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}"); - Assert.Equal("DefaultValue", obj.MyString); - Assert.Equal(2, obj.MyInts.Length); + // Collections are always serialized unless they have [JsonIgnore]. + Assert.Equal(@"{""MyInts"":[1,2]}", json); } [Fact] - public static void IgnoreReadOnlyProperties() + public static void IgnoreReadOnlyFields() { var options = new JsonSerializerOptions(); - options.IgnoreReadOnlyProperties = true; + options.IncludeFields = true; + options.IgnoreReadOnlyFields = true; - var obj = new ClassWithNoSetter(); + var obj = new ClassWithReadOnlyFields(); string json = JsonSerializer.Serialize(obj, options); @@ -711,6 +983,20 @@ public static void IgnoreReadOnlyProperties() Assert.Equal(@"{""MyInts"":[1,2]}", json); } + [Fact] + public static void NoSetter() + { + var obj = new ClassWithNoSetter(); + + string json = JsonSerializer.Serialize(obj); + Assert.Contains(@"""MyString"":""DefaultValue""", json); + Assert.Contains(@"""MyInts"":[1,2]", json); + + obj = JsonSerializer.Deserialize(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}"); + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal(2, obj.MyInts.Length); + } + [Fact] public static void NoGetter() { @@ -784,34 +1070,40 @@ private class NestedClass [Fact] public static void JsonIgnoreAttribute() { + var options = new JsonSerializerOptions { IncludeFields = true }; + // Verify default state. var obj = new ClassWithIgnoreAttributeProperty(); Assert.Equal(@"MyString", obj.MyString); Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); Assert.Equal(2, obj.MyStringsWithIgnore.Length); Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); + Assert.Equal(3.14M, obj.MyNumeric); // Verify serialize. - string json = JsonSerializer.Serialize(obj); + string json = JsonSerializer.Serialize(obj, options); Assert.Contains(@"""MyString""", json); Assert.DoesNotContain(@"MyStringWithIgnore", json); Assert.DoesNotContain(@"MyStringsWithIgnore", json); Assert.DoesNotContain(@"MyDictionaryWithIgnore", json); + Assert.DoesNotContain(@"MyNumeric", json); // Verify deserialize default. - obj = JsonSerializer.Deserialize(@"{}"); + obj = JsonSerializer.Deserialize(@"{}", options); Assert.Equal(@"MyString", obj.MyString); Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); Assert.Equal(2, obj.MyStringsWithIgnore.Length); Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); + Assert.Equal(3.14M, obj.MyNumeric); // Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore. obj = JsonSerializer.Deserialize( - @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""], ""MyDictionaryWithIgnore"":{""Key"":9}}"); + @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""], ""MyDictionaryWithIgnore"":{""Key"":9}, ""MyNumeric"": 2.71828}", options); Assert.Contains(@"Hello", obj.MyString); Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); Assert.Equal(2, obj.MyStringsWithIgnore.Length); Assert.Equal(1, obj.MyDictionaryWithIgnore["Key"]); + Assert.Equal(3.14M, obj.MyNumeric); } [Fact] @@ -986,6 +1278,18 @@ public void SetMyString(string value) } } + public class ClassWithReadOnlyFields + { + public ClassWithReadOnlyFields() + { + MyString = "DefaultValue"; + MyInts = new int[] { 1, 2 }; + } + + public readonly string MyString; + public readonly int[] MyInts; + } + public class ClassWithNoSetter { public ClassWithNoSetter() @@ -1052,6 +1356,7 @@ public ClassWithIgnoreAttributeProperty() MyString = "MyString"; MyStringWithIgnore = "MyStringWithIgnore"; MyStringsWithIgnore = new string[] { "1", "2" }; + MyNumeric = 3.14M; } [JsonIgnore] @@ -1064,6 +1369,9 @@ public ClassWithIgnoreAttributeProperty() [JsonIgnore] public string[] MyStringsWithIgnore { get; set; } + + [JsonIgnore] + public decimal MyNumeric; } private enum MyEnum @@ -1489,60 +1797,117 @@ public class ClassUsingIgnoreNeverAttribute } [Fact] - public static void IgnoreConditionNever_WinsOver_IgnoreReadOnlyValues() + public static void IgnoreConditionNever_WinsOver_IgnoreReadOnlyProperties() { var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; // Baseline - string json = JsonSerializer.Serialize(new ClassWithReadOnlyString("Hello"), options); + string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty("Hello"), options); Assert.Equal("{}", json); // With condition to never ignore - json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreNever("Hello"), options); + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreNever("Hello"), options); Assert.Equal(@"{""MyString"":""Hello""}", json); - json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreNever(null), options); + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreNever(null), options); Assert.Equal(@"{""MyString"":null}", json); } [Fact] - public static void IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyValues() + public static void IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyProperties() { var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; // Baseline - string json = JsonSerializer.Serialize(new ClassWithReadOnlyString("Hello"), options); + string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty("Hello"), options); Assert.Equal("{}", json); // With condition to ignore when null - json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreWhenWritingDefault("Hello"), options); + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault("Hello"), options); Assert.Equal(@"{""MyString"":""Hello""}", json); - json = JsonSerializer.Serialize(new ClassWithReadOnlyString_IgnoreWhenWritingDefault(null), options); + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault(null), options); Assert.Equal(@"{}", json); } - private class ClassWithReadOnlyString + [Fact] + public static void IgnoreConditionNever_WinsOver_IgnoreReadOnlyFields() + { + var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; + + // Baseline + string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField("Hello"), options); + Assert.Equal("{}", json); + + // With condition to never ignore + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreNever("Hello"), options); + Assert.Equal(@"{""MyString"":""Hello""}", json); + + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreNever(null), options); + Assert.Equal(@"{""MyString"":null}", json); + } + + [Fact] + public static void IgnoreConditionWhenWritingDefault_WinsOver_IgnoreReadOnlyFields() + { + var options = new JsonSerializerOptions { IgnoreReadOnlyProperties = true }; + + // Baseline + string json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField("Hello"), options); + Assert.Equal("{}", json); + + // With condition to ignore when null + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreWhenWritingDefault("Hello"), options); + Assert.Equal(@"{""MyString"":""Hello""}", json); + + json = JsonSerializer.Serialize(new ClassWithReadOnlyStringField_IgnoreWhenWritingDefault(null), options); + Assert.Equal(@"{}", json); + } + + private class ClassWithReadOnlyStringProperty + { + public string MyString { get; } + + public ClassWithReadOnlyStringProperty(string myString) => MyString = myString; + } + + private class ClassWithReadOnlyStringProperty_IgnoreNever + { + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string MyString { get; } + + public ClassWithReadOnlyStringProperty_IgnoreNever(string myString) => MyString = myString; + } + + private class ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string MyString { get; } + + public ClassWithReadOnlyStringProperty_IgnoreWhenWritingDefault(string myString) => MyString = myString; + } + + private class ClassWithReadOnlyStringField { public string MyString { get; } - public ClassWithReadOnlyString(string myString) => MyString = myString; + public ClassWithReadOnlyStringField(string myString) => MyString = myString; } - private class ClassWithReadOnlyString_IgnoreNever + private class ClassWithReadOnlyStringField_IgnoreNever { [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public string MyString { get; } - public ClassWithReadOnlyString_IgnoreNever(string myString) => MyString = myString; + public ClassWithReadOnlyStringField_IgnoreNever(string myString) => MyString = myString; } - private class ClassWithReadOnlyString_IgnoreWhenWritingDefault + private class ClassWithReadOnlyStringField_IgnoreWhenWritingDefault { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string MyString { get; } - public ClassWithReadOnlyString_IgnoreWhenWritingDefault(string myString) => MyString = myString; + public ClassWithReadOnlyStringField_IgnoreWhenWritingDefault(string myString) => MyString = myString; } [Fact] diff --git a/src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.Deserialize.cs b/src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.Deserialize.cs index 1de6a6808ac842..88586b872d8def 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.Deserialize.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.Deserialize.cs @@ -1199,7 +1199,7 @@ public static void ReferenceObjectBeforePreservedObject() [MemberData(nameof(ReadSuccessCases))] public static void ReadTestClassesWithExtensionOption(Type classType, byte[] data) { - var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve }; + var options = new JsonSerializerOptions { IncludeFields = true, ReferenceHandler = ReferenceHandler.Preserve }; object obj = JsonSerializer.Deserialize(data, classType, options); Assert.IsAssignableFrom(obj); ((ITestClass)obj).Verify(); diff --git a/src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs b/src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs index 2d01d6e67506c0..cb6d24ae9a73b3 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/SpanTests.cs @@ -20,7 +20,8 @@ public static void ParseNullTypeFail() [MemberData(nameof(ReadSuccessCases))] public static void Read(Type classType, byte[] data) { - object obj = JsonSerializer.Deserialize(data, classType); + var options = new JsonSerializerOptions { IncludeFields = true }; + object obj = JsonSerializer.Deserialize(data, classType, options); Assert.IsAssignableFrom(obj); ((ITestClass)obj).Verify(); } @@ -30,9 +31,11 @@ public static void Read(Type classType, byte[] data) public static void ReadFromStream(Type classType, byte[] data) { MemoryStream stream = new MemoryStream(data); + var options = new JsonSerializerOptions { IncludeFields = true }; object obj = JsonSerializer.DeserializeAsync( stream, - classType).Result; + classType, + options).Result; Assert.IsAssignableFrom(obj); ((ITestClass)obj).Verify(); @@ -42,7 +45,7 @@ public static void ReadFromStream(Type classType, byte[] data) obj = JsonSerializer.DeserializeAsync( stream, classType, - new JsonSerializerOptions { DefaultBufferSize = 5 }).Result; + new JsonSerializerOptions { DefaultBufferSize = 5, IncludeFields = true }).Result; Assert.IsAssignableFrom(obj); ((ITestClass)obj).Verify(); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithFields.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithFields.cs new file mode 100644 index 00000000000000..73256d2147ea73 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.SimpleTestClassWithFields.cs @@ -0,0 +1,511 @@ +// 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. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public class SimpleTestClassWithFields : ITestClass + { + public short MyInt16; + public int MyInt32; + public long MyInt64; + public ushort MyUInt16; + public uint MyUInt32; + public ulong MyUInt64; + public byte MyByte; + public sbyte MySByte; + public char MyChar; + public string MyString; + public decimal MyDecimal; + public bool MyBooleanTrue; + public bool MyBooleanFalse; + public float MySingle; + public double MyDouble; + public DateTime MyDateTime; + public DateTimeOffset MyDateTimeOffset; + public Guid MyGuid; + public Uri MyUri; + public SampleEnumSByte MySByteEnum; + public SampleEnumByte MyByteEnum; + public SampleEnum MyEnum; + public SampleEnumInt16 MyInt16Enum; + public SampleEnumInt32 MyInt32Enum; + public SampleEnumInt64 MyInt64Enum; + public SampleEnumUInt16 MyUInt16Enum; + public SampleEnumUInt32 MyUInt32Enum; + public SampleEnumUInt64 MyUInt64Enum; + public SimpleStruct MySimpleStruct; + public SimpleTestStruct MySimpleTestStruct; + public short[] MyInt16Array; + public int[] MyInt32Array; + public long[] MyInt64Array; + public ushort[] MyUInt16Array; + public uint[] MyUInt32Array; + public ulong[] MyUInt64Array; + public byte[] MyByteArray; + public sbyte[] MySByteArray; + public char[] MyCharArray; + public string[] MyStringArray; + public decimal[] MyDecimalArray; + public bool[] MyBooleanTrueArray; + public bool[] MyBooleanFalseArray; + public float[] MySingleArray; + public double[] MyDoubleArray; + public DateTime[] MyDateTimeArray; + public DateTimeOffset[] MyDateTimeOffsetArray; + public Guid[] MyGuidArray; + public Uri[] MyUriArray; + public SampleEnum[] MyEnumArray; + public int[][] MyInt16TwoDimensionArray; + public List> MyInt16TwoDimensionList; + public int[][][] MyInt16ThreeDimensionArray; + public List>> MyInt16ThreeDimensionList; + public List MyStringList; + public IEnumerable MyStringIEnumerable; + public IList MyStringIList; + public ICollection MyStringICollection; + public IEnumerable MyStringIEnumerableT; + public IList MyStringIListT; + public ICollection MyStringICollectionT; + public IReadOnlyCollection MyStringIReadOnlyCollectionT; + public IReadOnlyList MyStringIReadOnlyListT; + public ISet MyStringISetT; + public KeyValuePair MyStringToStringKeyValuePair; + public IDictionary MyStringToStringIDict; + public Dictionary MyStringToStringGenericDict; + public IDictionary MyStringToStringGenericIDict; + public IReadOnlyDictionary MyStringToStringGenericIReadOnlyDict; + public ImmutableDictionary MyStringToStringImmutableDict; + public IImmutableDictionary MyStringToStringIImmutableDict; + public ImmutableSortedDictionary MyStringToStringImmutableSortedDict; + public Stack MyStringStackT; + public Queue MyStringQueueT; + public HashSet MyStringHashSetT; + public LinkedList MyStringLinkedListT; + public SortedSet MyStringSortedSetT; + public IImmutableList MyStringIImmutableListT; + public IImmutableStack MyStringIImmutableStackT; + public IImmutableQueue MyStringIImmutableQueueT; + public IImmutableSet MyStringIImmutableSetT; + public ImmutableHashSet MyStringImmutableHashSetT; + public ImmutableList MyStringImmutableListT; + public ImmutableStack MyStringImmutableStackT; + public ImmutableQueue MyStringImmutablQueueT; + public ImmutableSortedSet MyStringImmutableSortedSetT; + public List MyListOfNullString; + + public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}"; + public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}"; + + private const string s_partialJsonProperties = + @"""MyInt16"" : 1," + + @"""MyInt32"" : 2," + + @"""MyInt64"" : 3," + + @"""MyUInt16"" : 4," + + @"""MyUInt32"" : 5," + + @"""MyUInt64"" : 6," + + @"""MyByte"" : 7," + + @"""MySByte"" : 8," + + @"""MyChar"" : ""a""," + + @"""MyString"" : ""Hello""," + + @"""MyBooleanTrue"" : true," + + @"""MyBooleanFalse"" : false," + + @"""MySingle"" : 1.1," + + @"""MyDouble"" : 2.2," + + @"""MyDecimal"" : 3.3," + + @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + + @"""MyGuid"" : ""1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6""," + + @"""MyUri"" : ""https://github.com/dotnet/runtime""," + + @"""MyEnum"" : 2," + // int by default + @"""MyInt64Enum"" : -9223372036854775808," + + @"""MyUInt64Enum"" : 18446744073709551615," + + @"""MyStringToStringKeyValuePair"" : {""Key"" : ""myKey"", ""Value"" : ""myValue""}," + + @"""MyStringToStringIDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericIDict"" : {""key"" : ""value""}," + + @"""MyStringToStringGenericIReadOnlyDict"" : {""key"" : ""value""}," + + @"""MyStringToStringImmutableDict"" : {""key"" : ""value""}," + + @"""MyStringToStringIImmutableDict"" : {""key"" : ""value""}," + + @"""MyStringToStringImmutableSortedDict"" : {""key"" : ""value""}," + + @"""MySimpleStruct"" : {""One"" : 11, ""Two"" : 1.9999, ""Three"" : 33}," + + @"""MySimpleTestStruct"" : {""MyInt64"" : 64, ""MyString"" :""Hello"", ""MyInt32Array"" : [32]}"; + + private const string s_partialJsonArrays = + @"""MyInt16Array"" : [1]," + + @"""MyInt32Array"" : [2]," + + @"""MyInt64Array"" : [3]," + + @"""MyUInt16Array"" : [4]," + + @"""MyUInt32Array"" : [5]," + + @"""MyUInt64Array"" : [6]," + + @"""MyByteArray"" : ""Bw==""," + // Base64 encoded value of 7 + @"""MySByteArray"" : [8]," + + @"""MyCharArray"" : [""a""]," + + @"""MyStringArray"" : [""Hello""]," + + @"""MyBooleanTrueArray"" : [true]," + + @"""MyBooleanFalseArray"" : [false]," + + @"""MySingleArray"" : [1.1]," + + @"""MyDoubleArray"" : [2.2]," + + @"""MyDecimalArray"" : [3.3]," + + @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + + @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," + + @"""MyGuidArray"" : [""1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6""]," + + @"""MyUriArray"" : [""https://github.com/dotnet/runtime""]," + + @"""MyEnumArray"" : [2]," + // int by default + @"""MyInt16TwoDimensionArray"" : [[10, 11],[20, 21]]," + + @"""MyInt16TwoDimensionList"" : [[10, 11],[20, 21]]," + + @"""MyInt16ThreeDimensionArray"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," + + @"""MyInt16ThreeDimensionList"" : [[[11, 12],[13, 14]],[[21,22],[23,24]]]," + + @"""MyStringList"" : [""Hello""]," + + @"""MyStringIEnumerable"" : [""Hello""]," + + @"""MyStringIList"" : [""Hello""]," + + @"""MyStringICollection"" : [""Hello""]," + + @"""MyStringIEnumerableT"" : [""Hello""]," + + @"""MyStringIListT"" : [""Hello""]," + + @"""MyStringICollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyListT"" : [""Hello""]," + + @"""MyStringISetT"" : [""Hello""]," + + @"""MyStringStackT"" : [""Hello"", ""World""]," + + @"""MyStringQueueT"" : [""Hello"", ""World""]," + + @"""MyStringHashSetT"" : [""Hello""]," + + @"""MyStringLinkedListT"" : [""Hello""]," + + @"""MyStringSortedSetT"" : [""Hello""]," + + @"""MyStringIImmutableListT"" : [""Hello""]," + + @"""MyStringIImmutableStackT"" : [""Hello""]," + + @"""MyStringIImmutableQueueT"" : [""Hello""]," + + @"""MyStringIImmutableSetT"" : [""Hello""]," + + @"""MyStringImmutableHashSetT"" : [""Hello""]," + + @"""MyStringImmutableListT"" : [""Hello""]," + + @"""MyStringImmutableStackT"" : [""Hello""]," + + @"""MyStringImmutablQueueT"" : [""Hello""]," + + @"""MyStringImmutableSortedSetT"" : [""Hello""]," + + @"""MyListOfNullString"" : [null]"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + + public void Initialize() + { + MyInt16 = 1; + MyInt32 = 2; + MyInt64 = 3; + MyUInt16 = 4; + MyUInt32 = 5; + MyUInt64 = 6; + MyByte = 7; + MySByte = 8; + MyChar = 'a'; + MyString = "Hello"; + MyBooleanTrue = true; + MyBooleanFalse = false; + MySingle = 1.1f; + MyDouble = 2.2d; + MyDecimal = 3.3m; + MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); + MyDateTimeOffset = new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)); + MyGuid = new Guid("1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6"); + MyUri = new Uri("https://github.com/dotnet/runtime"); + MyEnum = SampleEnum.Two; + MyInt64Enum = SampleEnumInt64.MinNegative; + MyUInt64Enum = SampleEnumUInt64.Max; + MyInt16Array = new short[] { 1 }; + MyInt32Array = new int[] { 2 }; + MyInt64Array = new long[] { 3 }; + MyUInt16Array = new ushort[] { 4 }; + MyUInt32Array = new uint[] { 5 }; + MyUInt64Array = new ulong[] { 6 }; + MyByteArray = new byte[] { 7 }; + MySByteArray = new sbyte[] { 8 }; + MyCharArray = new char[] { 'a' }; + MyStringArray = new string[] { "Hello" }; + MyBooleanTrueArray = new bool[] { true }; + MyBooleanFalseArray = new bool[] { false }; + MySingleArray = new float[] { 1.1f }; + MyDoubleArray = new double[] { 2.2d }; + MyDecimalArray = new decimal[] { 3.3m }; + MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; + MyDateTimeOffsetArray = new DateTimeOffset[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) }; + MyGuidArray = new Guid[] { new Guid("1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6") }; + MyUriArray = new Uri[] { new Uri("https://github.com/dotnet/runtime") }; + MyEnumArray = new SampleEnum[] { SampleEnum.Two }; + MySimpleStruct = new SimpleStruct { One = 11, Two = 1.9999 }; + MySimpleTestStruct = new SimpleTestStruct { MyInt64 = 64, MyString = "Hello", MyInt32Array = new int[] { 32 } }; + + MyInt16TwoDimensionArray = new int[2][]; + MyInt16TwoDimensionArray[0] = new int[] { 10, 11 }; + MyInt16TwoDimensionArray[1] = new int[] { 20, 21 }; + + MyInt16TwoDimensionList = new List>(); + MyInt16TwoDimensionList.Add(new List { 10, 11 }); + MyInt16TwoDimensionList.Add(new List { 20, 21 }); + + MyInt16ThreeDimensionArray = new int[2][][]; + MyInt16ThreeDimensionArray[0] = new int[2][]; + MyInt16ThreeDimensionArray[1] = new int[2][]; + MyInt16ThreeDimensionArray[0][0] = new int[] { 11, 12 }; + MyInt16ThreeDimensionArray[0][1] = new int[] { 13, 14 }; + MyInt16ThreeDimensionArray[1][0] = new int[] { 21, 22 }; + MyInt16ThreeDimensionArray[1][1] = new int[] { 23, 24 }; + + MyInt16ThreeDimensionList = new List>>(); + var list1 = new List>(); + MyInt16ThreeDimensionList.Add(list1); + list1.Add(new List { 11, 12 }); + list1.Add(new List { 13, 14 }); + var list2 = new List>(); + MyInt16ThreeDimensionList.Add(list2); + list2.Add(new List { 21, 22 }); + list2.Add(new List { 23, 24 }); + + MyStringList = new List() { "Hello" }; + + MyStringIEnumerable = new string[] { "Hello" }; + MyStringIList = new string[] { "Hello" }; + MyStringICollection = new string[] { "Hello" }; + + MyStringIEnumerableT = new string[] { "Hello" }; + MyStringIListT = new string[] { "Hello" }; + MyStringICollectionT = new string[] { "Hello" }; + MyStringIReadOnlyCollectionT = new string[] { "Hello" }; + MyStringIReadOnlyListT = new string[] { "Hello" }; + MyStringISetT = new HashSet { "Hello" }; + + MyStringToStringKeyValuePair = new KeyValuePair("myKey", "myValue"); + MyStringToStringIDict = new Dictionary { { "key", "value" } }; + + MyStringToStringGenericDict = new Dictionary { { "key", "value" } }; + MyStringToStringGenericIDict = new Dictionary { { "key", "value" } }; + MyStringToStringGenericIReadOnlyDict = new Dictionary { { "key", "value" } }; + + MyStringToStringImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringGenericDict); + MyStringToStringIImmutableDict = ImmutableDictionary.CreateRange(MyStringToStringGenericDict); + MyStringToStringImmutableSortedDict = ImmutableSortedDictionary.CreateRange(MyStringToStringGenericDict); + + MyStringStackT = new Stack(new List() { "Hello", "World" }); + MyStringQueueT = new Queue(new List() { "Hello", "World" }); + MyStringHashSetT = new HashSet(new List() { "Hello" }); + MyStringLinkedListT = new LinkedList(new List() { "Hello" }); + MyStringSortedSetT = new SortedSet(new List() { "Hello" }); + + MyStringIImmutableListT = ImmutableList.CreateRange(new List { "Hello" }); + MyStringIImmutableStackT = ImmutableStack.CreateRange(new List { "Hello" }); + MyStringIImmutableQueueT = ImmutableQueue.CreateRange(new List { "Hello" }); + MyStringIImmutableSetT = ImmutableHashSet.CreateRange(new List { "Hello" }); + MyStringImmutableHashSetT = ImmutableHashSet.CreateRange(new List { "Hello" }); + MyStringImmutableListT = ImmutableList.CreateRange(new List { "Hello" }); + MyStringImmutableStackT = ImmutableStack.CreateRange(new List { "Hello" }); + MyStringImmutablQueueT = ImmutableQueue.CreateRange(new List { "Hello" }); + MyStringImmutableSortedSetT = ImmutableSortedSet.CreateRange(new List { "Hello" }); + + MyListOfNullString = new List { null }; + } + + public void Verify() + { + Assert.Equal((short)1, MyInt16); + Assert.Equal((int)2, MyInt32); + Assert.Equal((long)3, MyInt64); + Assert.Equal((ushort)4, MyUInt16); + Assert.Equal((uint)5, MyUInt32); + Assert.Equal((ulong)6, MyUInt64); + Assert.Equal((byte)7, MyByte); + Assert.Equal((sbyte)8, MySByte); + Assert.Equal('a', MyChar); + Assert.Equal("Hello", MyString); + Assert.Equal(3.3m, MyDecimal); + Assert.False(MyBooleanFalse); + Assert.True(MyBooleanTrue); + Assert.Equal(1.1f, MySingle); + Assert.Equal(2.2d, MyDouble); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffset); + Assert.Equal(new Guid("1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6"), MyGuid); + Assert.Equal(new Uri("https://github.com/dotnet/runtime"), MyUri); + Assert.Equal(SampleEnum.Two, MyEnum); + Assert.Equal(SampleEnumInt64.MinNegative, MyInt64Enum); + Assert.Equal(SampleEnumUInt64.Max, MyUInt64Enum); + Assert.Equal(11, MySimpleStruct.One); + Assert.Equal(1.9999, MySimpleStruct.Two); + Assert.Equal(64, MySimpleTestStruct.MyInt64); + Assert.Equal("Hello", MySimpleTestStruct.MyString); + Assert.Equal(32, MySimpleTestStruct.MyInt32Array[0]); + + Assert.Equal((short)1, MyInt16Array[0]); + Assert.Equal((int)2, MyInt32Array[0]); + Assert.Equal((long)3, MyInt64Array[0]); + Assert.Equal((ushort)4, MyUInt16Array[0]); + Assert.Equal((uint)5, MyUInt32Array[0]); + Assert.Equal((ulong)6, MyUInt64Array[0]); + Assert.Equal((byte)7, MyByteArray[0]); + Assert.Equal((sbyte)8, MySByteArray[0]); + Assert.Equal('a', MyCharArray[0]); + Assert.Equal("Hello", MyStringArray[0]); + Assert.Equal(3.3m, MyDecimalArray[0]); + Assert.False(MyBooleanFalseArray[0]); + Assert.True(MyBooleanTrueArray[0]); + Assert.Equal(1.1f, MySingleArray[0]); + Assert.Equal(2.2d, MyDoubleArray[0]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]); + Assert.Equal(new Guid("1B33498A-7B7D-4DDA-9C13-F6AA4AB449A6"), MyGuidArray[0]); + Assert.Equal(new Uri("https://github.com/dotnet/runtime"), MyUriArray[0]); + Assert.Equal(SampleEnum.Two, MyEnumArray[0]); + + Assert.Equal(10, MyInt16TwoDimensionArray[0][0]); + Assert.Equal(11, MyInt16TwoDimensionArray[0][1]); + Assert.Equal(20, MyInt16TwoDimensionArray[1][0]); + Assert.Equal(21, MyInt16TwoDimensionArray[1][1]); + + Assert.Equal(10, MyInt16TwoDimensionList[0][0]); + Assert.Equal(11, MyInt16TwoDimensionList[0][1]); + Assert.Equal(20, MyInt16TwoDimensionList[1][0]); + Assert.Equal(21, MyInt16TwoDimensionList[1][1]); + + Assert.Equal(11, MyInt16ThreeDimensionArray[0][0][0]); + Assert.Equal(12, MyInt16ThreeDimensionArray[0][0][1]); + Assert.Equal(13, MyInt16ThreeDimensionArray[0][1][0]); + Assert.Equal(14, MyInt16ThreeDimensionArray[0][1][1]); + Assert.Equal(21, MyInt16ThreeDimensionArray[1][0][0]); + Assert.Equal(22, MyInt16ThreeDimensionArray[1][0][1]); + Assert.Equal(23, MyInt16ThreeDimensionArray[1][1][0]); + Assert.Equal(24, MyInt16ThreeDimensionArray[1][1][1]); + + Assert.Equal(11, MyInt16ThreeDimensionList[0][0][0]); + Assert.Equal(12, MyInt16ThreeDimensionList[0][0][1]); + Assert.Equal(13, MyInt16ThreeDimensionList[0][1][0]); + Assert.Equal(14, MyInt16ThreeDimensionList[0][1][1]); + Assert.Equal(21, MyInt16ThreeDimensionList[1][0][0]); + Assert.Equal(22, MyInt16ThreeDimensionList[1][0][1]); + Assert.Equal(23, MyInt16ThreeDimensionList[1][1][0]); + Assert.Equal(24, MyInt16ThreeDimensionList[1][1][1]); + + Assert.Equal("Hello", MyStringList[0]); + + IEnumerator enumerator = MyStringIEnumerable.GetEnumerator(); + enumerator.MoveNext(); + { + // Verifying after deserialization. + if (enumerator.Current is JsonElement currentJsonElement) + { + Assert.Equal("Hello", currentJsonElement.GetString()); + } + // Verifying test data. + else + { + Assert.Equal("Hello", enumerator.Current); + } + } + + { + // Verifying after deserialization. + if (MyStringIList[0] is JsonElement currentJsonElement) + { + Assert.Equal("Hello", currentJsonElement.GetString()); + } + // Verifying test data. + else + { + Assert.Equal("Hello", enumerator.Current); + } + } + + enumerator = MyStringICollection.GetEnumerator(); + enumerator.MoveNext(); + { + // Verifying after deserialization. + if (enumerator.Current is JsonElement currentJsonElement) + { + Assert.Equal("Hello", currentJsonElement.GetString()); + } + // Verifying test data. + else + { + Assert.Equal("Hello", enumerator.Current); + } + } + + Assert.Equal("Hello", MyStringIEnumerableT.First()); + Assert.Equal("Hello", MyStringIListT[0]); + Assert.Equal("Hello", MyStringICollectionT.First()); + Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First()); + Assert.Equal("Hello", MyStringIReadOnlyListT[0]); + Assert.Equal("Hello", MyStringISetT.First()); + + enumerator = MyStringToStringIDict.GetEnumerator(); + enumerator.MoveNext(); + { + // Verifying after deserialization. + if (enumerator.Current is JsonElement currentJsonElement) + { + IEnumerator jsonEnumerator = currentJsonElement.EnumerateObject(); + jsonEnumerator.MoveNext(); + + JsonProperty property = (JsonProperty)jsonEnumerator.Current; + + Assert.Equal("key", property.Name); + Assert.Equal("value", property.Value.GetString()); + } + // Verifying test data. + else + { + DictionaryEntry entry = (DictionaryEntry)enumerator.Current; + Assert.Equal("key", entry.Key); + + if (entry.Value is JsonElement element) + { + Assert.Equal("value", element.GetString()); + } + else + { + Assert.Equal("value", entry.Value); + } + } + } + + Assert.Equal("value", MyStringToStringGenericDict["key"]); + Assert.Equal("value", MyStringToStringGenericIDict["key"]); + Assert.Equal("value", MyStringToStringGenericIReadOnlyDict["key"]); + + Assert.Equal("value", MyStringToStringImmutableDict["key"]); + Assert.Equal("value", MyStringToStringIImmutableDict["key"]); + Assert.Equal("value", MyStringToStringImmutableSortedDict["key"]); + + Assert.Equal("myKey", MyStringToStringKeyValuePair.Key); + Assert.Equal("myValue", MyStringToStringKeyValuePair.Value); + + Assert.Equal(2, MyStringStackT.Count); + Assert.True(MyStringStackT.Contains("Hello")); + Assert.True(MyStringStackT.Contains("World")); + + string[] expectedQueue = { "Hello", "World" }; + int i = 0; + foreach (string item in MyStringQueueT) + { + Assert.Equal(expectedQueue[i], item); + i++; + } + + Assert.Equal("Hello", MyStringHashSetT.First()); + Assert.Equal("Hello", MyStringLinkedListT.First()); + Assert.Equal("Hello", MyStringSortedSetT.First()); + + Assert.Equal("Hello", MyStringIImmutableListT[0]); + Assert.Equal("Hello", MyStringIImmutableStackT.First()); + Assert.Equal("Hello", MyStringIImmutableQueueT.First()); + Assert.Equal("Hello", MyStringIImmutableSetT.First()); + Assert.Equal("Hello", MyStringImmutableHashSetT.First()); + Assert.Equal("Hello", MyStringImmutableListT[0]); + Assert.Equal("Hello", MyStringImmutableStackT.First()); + Assert.Equal("Hello", MyStringImmutablQueueT.First()); + Assert.Equal("Hello", MyStringImmutableSortedSetT.First()); + + Assert.Null(MyListOfNullString[0]); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.SimpleTestStructWithFields.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.SimpleTestStructWithFields.cs new file mode 100644 index 00000000000000..61b9a028f7844d --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.SimpleTestStructWithFields.cs @@ -0,0 +1,221 @@ +// 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. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public struct SimpleTestStructWithFields : ITestClass + { + public short MyInt16; + public int MyInt32; + public long MyInt64; + public ushort MyUInt16; + public uint MyUInt32; + public ulong MyUInt64; + public byte MyByte; + public sbyte MySByte; + public char MyChar; + public string MyString; + public decimal MyDecimal; + public bool MyBooleanTrue; + public bool MyBooleanFalse; + public float MySingle; + public double MyDouble; + public DateTime MyDateTime; + public DateTimeOffset MyDateTimeOffset; + public SampleEnum MyEnum; + public SampleEnumInt64 MyInt64Enum; + public SampleEnumUInt64 MyUInt64Enum; + public SimpleStruct MySimpleStruct; + public SimpleTestClass MySimpleTestClass; + public short[] MyInt16Array; + public int[] MyInt32Array; + public long[] MyInt64Array; + public ushort[] MyUInt16Array; + public uint[] MyUInt32Array; + public ulong[] MyUInt64Array; + public byte[] MyByteArray; + public sbyte[] MySByteArray; + public char[] MyCharArray; + public string[] MyStringArray; + public decimal[] MyDecimalArray; + public bool[] MyBooleanTrueArray; + public bool[] MyBooleanFalseArray; + public float[] MySingleArray; + public double[] MyDoubleArray; + public DateTime[] MyDateTimeArray; + public DateTimeOffset[] MyDateTimeOffsetArray; + public SampleEnum[] MyEnumArray; + public List MyStringList; + public IEnumerable MyStringIEnumerableT; + public IList MyStringIListT; + public ICollection MyStringICollectionT; + public IReadOnlyCollection MyStringIReadOnlyCollectionT; + public IReadOnlyList MyStringIReadOnlyListT; + public ISet MyStringISetT; + + public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}"; + public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}"; + + private const string s_partialJsonProperties = + @"""MyInt16"" : 1," + + @"""MyInt32"" : 2," + + @"""MyInt64"" : 3," + + @"""MyUInt16"" : 4," + + @"""MyUInt32"" : 5," + + @"""MyUInt64"" : 6," + + @"""MyByte"" : 7," + + @"""MySByte"" : 8," + + @"""MyChar"" : ""a""," + + @"""MyString"" : ""Hello""," + + @"""MyBooleanTrue"" : true," + + @"""MyBooleanFalse"" : false," + + @"""MySingle"" : 1.1," + + @"""MyDouble"" : 2.2," + + @"""MyDecimal"" : 3.3," + + @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + + @"""MyEnum"" : 2," + // int by default + @"""MyInt64Enum"" : -9223372036854775808," + + @"""MyUInt64Enum"" : 18446744073709551615," + + @"""MySimpleStruct"" : {""One"" : 11, ""Two"" : 1.9999, ""Three"" : 33}"; + + private const string s_partialJsonArrays = + @"""MyInt16Array"" : [1]," + + @"""MyInt32Array"" : [2]," + + @"""MyInt64Array"" : [3]," + + @"""MyUInt16Array"" : [4]," + + @"""MyUInt32Array"" : [5]," + + @"""MyUInt64Array"" : [6]," + + @"""MyByteArray"" : ""Bw==""," + // Base64 encoded value of 7 + @"""MySByteArray"" : [8]," + + @"""MyCharArray"" : [""a""]," + + @"""MyStringArray"" : [""Hello""]," + + @"""MyBooleanTrueArray"" : [true]," + + @"""MyBooleanFalseArray"" : [false]," + + @"""MySingleArray"" : [1.1]," + + @"""MyDoubleArray"" : [2.2]," + + @"""MyDecimalArray"" : [3.3]," + + @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + + @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," + + @"""MyEnumArray"" : [2]," + // int by default + @"""MyStringList"" : [""Hello""]," + + @"""MyStringIEnumerableT"" : [""Hello""]," + + @"""MyStringIListT"" : [""Hello""]," + + @"""MyStringICollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyListT"" : [""Hello""]," + + @"""MyStringISetT"" : [""Hello""]"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + + public void Initialize() + { + MyInt16 = 1; + MyInt32 = 2; + MyInt64 = 3; + MyUInt16 = 4; + MyUInt32 = 5; + MyUInt64 = 6; + MyByte = 7; + MySByte = 8; + MyChar = 'a'; + MyString = "Hello"; + MyBooleanTrue = true; + MyBooleanFalse = false; + MySingle = 1.1f; + MyDouble = 2.2d; + MyDecimal = 3.3m; + MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); + MyDateTimeOffset = new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)); + MyEnum = SampleEnum.Two; + MyInt64Enum = SampleEnumInt64.MinNegative; + MyUInt64Enum = SampleEnumUInt64.Max; + MySimpleStruct = new SimpleStruct { One = 11, Two = 1.9999 }; + + MyInt16Array = new short[] { 1 }; + MyInt32Array = new int[] { 2 }; + MyInt64Array = new long[] { 3 }; + MyUInt16Array = new ushort[] { 4 }; + MyUInt32Array = new uint[] { 5 }; + MyUInt64Array = new ulong[] { 6 }; + MyByteArray = new byte[] { 7 }; + MySByteArray = new sbyte[] { 8 }; + MyCharArray = new char[] { 'a' }; + MyStringArray = new string[] { "Hello" }; + MyBooleanTrueArray = new bool[] { true }; + MyBooleanFalseArray = new bool[] { false }; + MySingleArray = new float[] { 1.1f }; + MyDoubleArray = new double[] { 2.2d }; + MyDecimalArray = new decimal[] { 3.3m }; + MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; + MyDateTimeOffsetArray = new DateTimeOffset[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) }; + MyEnumArray = new SampleEnum[] { SampleEnum.Two }; + + MyStringList = new List() { "Hello" }; + MyStringIEnumerableT = new string[] { "Hello" }; + MyStringIListT = new string[] { "Hello" }; + MyStringICollectionT = new string[] { "Hello" }; + MyStringIReadOnlyCollectionT = new string[] { "Hello" }; + MyStringIReadOnlyListT = new string[] { "Hello" }; + MyStringISetT = new HashSet { "Hello" }; + } + + public void Verify() + { + Assert.Equal((short)1, MyInt16); + Assert.Equal((int)2, MyInt32); + Assert.Equal((long)3, MyInt64); + Assert.Equal((ushort)4, MyUInt16); + Assert.Equal((uint)5, MyUInt32); + Assert.Equal((ulong)6, MyUInt64); + Assert.Equal((byte)7, MyByte); + Assert.Equal((sbyte)8, MySByte); + Assert.Equal('a', MyChar); + Assert.Equal("Hello", MyString); + Assert.Equal(3.3m, MyDecimal); + Assert.False(MyBooleanFalse); + Assert.True(MyBooleanTrue); + Assert.Equal(1.1f, MySingle); + Assert.Equal(2.2d, MyDouble); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffset); + Assert.Equal(SampleEnum.Two, MyEnum); + Assert.Equal(SampleEnumInt64.MinNegative, MyInt64Enum); + Assert.Equal(SampleEnumUInt64.Max, MyUInt64Enum); + Assert.Equal(11, MySimpleStruct.One); + Assert.Equal(1.9999, MySimpleStruct.Two); + + Assert.Equal((short)1, MyInt16Array[0]); + Assert.Equal((int)2, MyInt32Array[0]); + Assert.Equal((long)3, MyInt64Array[0]); + Assert.Equal((ushort)4, MyUInt16Array[0]); + Assert.Equal((uint)5, MyUInt32Array[0]); + Assert.Equal((ulong)6, MyUInt64Array[0]); + Assert.Equal((byte)7, MyByteArray[0]); + Assert.Equal((sbyte)8, MySByteArray[0]); + Assert.Equal('a', MyCharArray[0]); + Assert.Equal("Hello", MyStringArray[0]); + Assert.Equal(3.3m, MyDecimalArray[0]); + Assert.False(MyBooleanFalseArray[0]); + Assert.True(MyBooleanTrueArray[0]); + Assert.Equal(1.1f, MySingleArray[0]); + Assert.Equal(2.2d, MyDoubleArray[0]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]); + Assert.Equal(SampleEnum.Two, MyEnumArray[0]); + + Assert.Equal("Hello", MyStringList[0]); + Assert.Equal("Hello", MyStringIEnumerableT.First()); + Assert.Equal("Hello", MyStringIListT[0]); + Assert.Equal("Hello", MyStringICollectionT.First()); + Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First()); + Assert.Equal("Hello", MyStringIReadOnlyListT[0]); + Assert.Equal("Hello", MyStringISetT.First()); + } + } +} diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs index 0eca0ba16706fc..c38e2365602681 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.cs @@ -1806,6 +1806,16 @@ public class ClassWithExtensionProperty public IDictionary MyOverflow { get; set; } } + public class ClassWithExtensionField + { + public SimpleTestClass MyNestedClass { get; set; } + public int MyInt { get; set; } + + [JsonInclude] + [JsonExtensionData] + public IDictionary MyOverflow; + } + public class TestClassWithNestedObjectCommentsInner : ITestClass { public SimpleTestClass MyData { get; set; } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestData.cs b/src/libraries/System.Text.Json/tests/Serialization/TestData.cs index 68b9b3ce93d120..5a43ffd9f372bb 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestData.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestData.cs @@ -13,7 +13,9 @@ public static IEnumerable ReadSuccessCases get { yield return new object[] { typeof(SimpleTestStruct), SimpleTestStruct.s_data }; + yield return new object[] { typeof(SimpleTestStructWithFields), SimpleTestStructWithFields.s_data }; yield return new object[] { typeof(SimpleTestClass), SimpleTestClass.s_data }; + yield return new object[] { typeof(SimpleTestClassWithFields), SimpleTestClassWithFields.s_data }; yield return new object[] { typeof(SimpleTestClassWithNullables), SimpleTestClassWithNullables.s_data }; yield return new object[] { typeof(SimpleTestClassWithNulls), SimpleTestClassWithNulls.s_data }; yield return new object[] { typeof(SimpleTestClassWithSimpleObject), SimpleTestClassWithSimpleObject.s_data }; @@ -51,12 +53,15 @@ public static IEnumerable ReadSuccessCases yield return new object[] { typeof(ClassWithComplexObjects), ClassWithComplexObjects.s_data }; } } + public static IEnumerable WriteSuccessCases { get { yield return new object[] { new SimpleTestStruct() }; + yield return new object[] { new SimpleTestStructWithFields() }; yield return new object[] { new SimpleTestClass() }; + yield return new object[] { new SimpleTestClassWithFields() }; yield return new object[] { new SimpleTestClassWithNullables() }; yield return new object[] { new SimpleTestClassWithNulls() }; yield return new object[] { new SimpleTestClassWithSimpleObject() }; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj index ddfccacb6f8a5e..a2f0dc94b050eb 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -118,11 +118,13 @@ + +