@@ -101,6 +101,9 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
101101 ? StringComparer . OrdinalIgnoreCase
102102 : StringComparer . Ordinal ) ;
103103
104+ HashSet < string > ? ignoredProperties = null ;
105+
106+ // We start from the most derived type and ascend to the base type.
104107 for ( Type ? currentType = type ; currentType != null ; currentType = currentType . BaseType )
105108 {
106109 foreach ( PropertyInfo propertyInfo in currentType . GetProperties ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ) )
@@ -111,44 +114,55 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
111114 continue ;
112115 }
113116
114- if ( IsNonPublicProperty ( propertyInfo ) )
115- {
116- if ( JsonPropertyInfo . GetAttribute < JsonIncludeAttribute > ( propertyInfo ) != null )
117- {
118- ThrowHelper . ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid ( propertyInfo , currentType ) ;
119- }
120-
121- // Non-public properties should not be included for (de)serialization.
122- continue ;
123- }
124-
125- // For now we only support public getters\setters
117+ // For now we only support public properties (i.e. setter and/or getter is public).
126118 if ( propertyInfo . GetMethod ? . IsPublic == true ||
127119 propertyInfo . SetMethod ? . IsPublic == true )
128120 {
129121 JsonPropertyInfo jsonPropertyInfo = AddProperty ( propertyInfo , currentType , options ) ;
130122 Debug . Assert ( jsonPropertyInfo != null && jsonPropertyInfo . NameAsString != null ) ;
131123
132- // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception.
124+ string propertyName = propertyInfo . Name ;
125+
126+ // The JsonPropertyNameAttribute or naming policy resulted in a collision.
133127 if ( ! JsonHelpers . TryAdd ( cache , jsonPropertyInfo . NameAsString , jsonPropertyInfo ) )
134128 {
135129 JsonPropertyInfo other = cache [ jsonPropertyInfo . NameAsString ] ;
136130
137- if ( other . ShouldDeserialize == false && other . ShouldSerialize == false )
131+ if ( other . IsIgnored )
138132 {
139- // Overwrite the one just added since it has [JsonIgnore].
133+ // Overwrite previously cached property since it has [JsonIgnore].
140134 cache [ jsonPropertyInfo . NameAsString ] = jsonPropertyInfo ;
141135 }
142- else if ( other . PropertyInfo ? . Name != jsonPropertyInfo . PropertyInfo ? . Name &&
143- ( jsonPropertyInfo . ShouldDeserialize == true || jsonPropertyInfo . ShouldSerialize == true ) )
136+ else if (
137+ // Does the current property have `JsonIgnoreAttribute`?
138+ ! jsonPropertyInfo . IsIgnored &&
139+ // Is the current property hidden by the previously cached property
140+ // (with `new` keyword, or by overriding)?
141+ other . PropertyInfo ! . Name != propertyName &&
142+ // Was a property with the same CLR name was ignored? That property hid the current property,
143+ // thus, if it was ignored, the current property should be ignored too.
144+ ignoredProperties ? . Contains ( propertyName ) != true )
144145 {
145- // Check for name equality is required to determine when a new slot is used for the member.
146- // Therefore, if names are not the same, there is conflict due to the name policy or attributes.
146+ // We throw if we have two public properties that have the same JSON property name, and neither have been ignored.
147147 ThrowHelper . ThrowInvalidOperationException_SerializerPropertyNameConflict ( Type , jsonPropertyInfo ) ;
148148 }
149- // else ignore jsonPropertyInfo since it has [JsonIgnore] or it's hidden by a new slot.
149+ // Ignore the current property.
150+ }
151+
152+ if ( jsonPropertyInfo . IsIgnored )
153+ {
154+ ( ignoredProperties ??= new HashSet < string > ( ) ) . Add ( propertyName ) ;
150155 }
151156 }
157+ else
158+ {
159+ if ( JsonPropertyInfo . GetAttribute < JsonIncludeAttribute > ( propertyInfo ) != null )
160+ {
161+ ThrowHelper . ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid ( propertyInfo , currentType ) ;
162+ }
163+
164+ // Non-public properties should not be included for (de)serialization.
165+ }
152166 }
153167 }
154168
@@ -211,13 +225,6 @@ public JsonClassInfo(Type type, JsonSerializerOptions options)
211225 }
212226 }
213227
214- private static bool IsNonPublicProperty ( PropertyInfo propertyInfo )
215- {
216- MethodInfo ? getMethod = propertyInfo . GetMethod ;
217- MethodInfo ? setMethod = propertyInfo . SetMethod ;
218- return ! ( ( getMethod != null && getMethod . IsPublic ) || ( setMethod != null && setMethod . IsPublic ) ) ;
219- }
220-
221228 private void InitializeConstructorParameters ( Dictionary < string , JsonPropertyInfo > propertyCache , ConstructorInfo constructorInfo )
222229 {
223230 ParameterInfo [ ] parameters = constructorInfo ! . GetParameters ( ) ;
0 commit comments