-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Expose ReferenceResolver and rename ReferenceHandling to ReferenceHandler #36829
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
|
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
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.
Is it OK to use null as the "default" or as the "do not preserve" option?
As per early discussions with @ahsonkhan, people tend to prefer actually seeing the default option listed, so this would only mask null value as the default, instead of having one instance that holds a ReferenceResolver that is never used.
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.
If Default is not functional, can we consider removing it from the API? The Default property on JsonNamingPolicy is internal.
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.
Does the change regress existing benchmarks (both with and without the feature)?
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.
Can you minimize the diff in this file by restricting changes just to the new API? We can follow up with a clean-up PR.
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.
Why is this nullable?
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.
Because it holds a null value on the implementation. As we discussed in another comment, this property can be just a facade for the default value for ReferenceHandler.
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.
I removed the property since as you mentioned in another comment, it is no longer functional, this still might need approval since we are changing the API.
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultReferenceResolver.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultReferenceResolver.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.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.
What's the limitation with setting the JSON path correctly?
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.
Before, AddReferenceOnDeserialize behave as a TryAdd method, where you could know if the new object was successfully added or not afterwards and we could throw in the call site instead of throwing in the method.
Now, AddReference is a void method that does not follow a TryAdd pattern so we should throw in its implementation, where the code is unaware of the state.
There are a few workaround to keep the behavior as it was before, e.g, we could set state.Current.JsonPropertyName = "$id" before calling AddReference and set it back to its original value after, but that seems quite ugly and no worth doing without discussion. That's why I left this comment here.
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.
Why would you have to set it back to the original value? Current is for the specific frame, and we are doing a forward-only read.
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.
As we discussed offline, there is one case where we are off of $id and we call AddReference: for a preserved array the last metadata we will read before calling AddReference would be $values, i.e. the reader is at the StartArray token when we call AddReference on the IEnumerableDefaultConverter.
As you suggested, we could potentially avoid the "hacky" thing of remember the last metadata if we call AddReference right when we read the $id property instead of waiting for $values. This probably requires splitting HandleMetadata implementation to instantiate the collection when we call HandleMetadata from IEnumerableDefaultconverter.
I have created issue #37168 to follow up on this.
src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReferenceResolver.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.cs
Outdated
Show resolved
Hide resolved
|
This change does not regress performance on the out-of-box path. ReadresultsBefore:
Now:
summary
WriteresultsBefore:
Now:
summary
|
|
For ReferenceHandler.Preserve, allocation increased by 40 bytes on read/write, this is because we replaced ReadresultsBefore:
Now:
summary
WriteresultsBefore:
Now:
summary
|
| bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences(); | ||
|
|
||
| if (!state.SupportContinuation && !shouldReadPreservedReferences) | ||
| if (!state.SupportContinuation && options.ReferenceHandler == null) |
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.
It looks like these 2 variables are always checked together. Although they could be separate in the future, consider collapsing then having state.SupportContinuation consider options.ReferencHandler factored in to prevent the &&
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.
As of today, state.SupportContinuation and options.ReferenceHandler need to go through the "slow path" since both relies on the state.Current.ObjectState.
Is your suggestion related to refactor the code so we can do a fast path for ReferenceHandler when there is no need for continuation? If that's the case I can try to tackle that in a follow-up PR.
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.
Not a fast path. Just having a single bool like
state.UseFastPath = !state.SupportContinuation && options.ReferenceHandler == null
|
|
||
| // Handle the metadata properties. | ||
| if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue) | ||
| if (options.ReferenceHandler != null && state.Current.ObjectState < StackFrameObjectState.PropertyValue) |
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.
Cache these in a bool (since accessed elsewhere in same method)?
...es/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs
Outdated
Show resolved
Hide resolved
| internal sealed class PreserveReferenceResolver : ReferenceResolver | ||
| { | ||
| private uint _referenceCount; | ||
| private readonly Dictionary<string, object>? _referenceIdToObjectMap; |
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.
I assume ConcurrentDictionary is not used is because we create a new instance for every (de)serialization call -- correct? i.e. we don't have to worry about multiple threads
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.
That's correct. We call CreateConverter that returns a new instance every time we (de)serialize unless someone provides its own ReferenceHandler implementation that does otherwise.
src/libraries/System.Text.Json/tests/Serialization/ReferenceHandlerTests.cs
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
Outdated
Show resolved
Hide resolved
…dler (dotnet#36829) * Expose ReferenceResolver and rename ReferenceHandling to ReferenceHandler * Address some feedback * Address feedback * Clean-up code * Change messages in string.resx * Add test for a badly implemented resolver * Address feedback.
Follow up to dotnet/runtime#37296 (comment) The property and the type ReferenceHandling were recently renamed to Referencehandler on dotnet/runtime#36829 Above PR was also ported to dotnet/runtime preview6 branch under dotnet/runtime#37296 therefore once this is into master, it should be ported to release/5.0-preview6 branch as well.
Address the following as it was detailed in #30820 (comment):
ReferenceHandlingto resemble a class.ReferenceResolverclass.ReferenceHandler<T> where T : ReferenceResolverto allow users to safely provide their own resolver implementation that will be instantiated on each call to (De)Serialize.ReferenceHandlerabstract so if you override it you can opt-in for using a persistent resolver.This PR effectively closes #30820 as no more changes would be pending to address.