-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Fix NullReference on JsonExtensionData #34569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
layomia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR. Just a few comments.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a test for deserialization that ensures that the converter is not called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prior to this PR the cache gets populated based on the converter that is specified by [JsonConverter] or through JsonSerializerOptions, but upon serialization/deserialization it is assuming that we have a JsonDictionaryConverter<>. My PR makes sure for writing we do not make that assumption, but for reading it is more complicated, because we store only one converter per property. If we want different behaviors for read and write, we need to have the custom serializer registered for write and JsonDictionaryConverter<> for read, at lease for the extension data.
Current code
JsonConverter<object> converter = (JsonConverter<object>)state.Current.JsonPropertyInfo!.RuntimeClassInfo.ElementClassInfo!.PropertyInfoForClassInfo.ConverterBase;Assumes that ElementClassInfo is not null which is not correct in this case and currently it throws with NullReferenceException so the suggested test would fail. I think better to fix that in a separate PR.
runtime/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
Line 219 in c653291
| JsonConverter<object> converter = (JsonConverter<object>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can mitigate the null ref by fetching the converter from the Options instance, rather than ElementClassInfo, similar to
runtime/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
Line 265 in c653291
| JsonConverter<JsonElement> converter = (JsonConverter<JsonElement>)Options.GetConverter(typeof(JsonElement)); |
We should make the change in this PR.
The other option is to assign ElementType when handling ClassType.Value types during warm up, similar to
runtime/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
Line 170 in c653291
| ElementType = converter.ElementType; |
We probably shouldn't go down this route since ElementType and ElementClassInfo are logically reserved for collections (de)serialized without custom converters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In light of this discussion, let's add and test two more classes for deserialization (where the extension data element type is JsonElement), to ensure that the converter (another class to add) not called.
- a test for deserialization where the converter is added to the
options.Converterscollection - a test for deserialization where
JsonConverterAttributeis placed on the property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated and tests are added
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ExtensionDataTests.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For simplicity, this should just be
JsonConverter<object> converter = (JsonConverter<object>)Options.GetConverter(typeof(object)));This change shouldn't have any noticeable perf impact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a similar change on line 237. The tests I suggest below will fail if this change isn't made.
JsonConverter<JsonElement> converter = (JsonConverter<JsonElement>)Options.GetConverter(typeof(JsonElement)));There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need another class where the extension property doesn't have the converter attribute and a test (for serialization and deserialization) where the converter is added at runtime with options.Converter.Add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For completeness, we should also add tests (serialization and deserialization) for a class where the extension property dictionary is a custom type, and the JsonConverterAttribute is applied on the custom type itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new test covers them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should test serialization here (Assert.Throws is fine).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new test covers both serialization and deserialization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
| Debug.Assert(!(value is null)); | |
| Debug.Assert(value != null); |
for consistency with the rest of the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
|
@layomia I changed the tests to ensure we cover all the combinations: |
Fix NullReferenceException on serialization when JsonExtensionData dictionary has a custom JsonConverter. Fix #32903
layomia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
It would be good to add a test where the converter is placed on the type itself as described in #34569 (comment). I'll pick this up in a follow up.
|
Test failure unrelated. |
Fix NullReferenceException on serialization when JsonExtensionData dictionary has a custom JsonConverter.
Fix #32903