Skip to content
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

Apply a number of improvements and bugfixes to JsonNode #88194

Merged
merged 4 commits into from Jul 1, 2023

Conversation

eiriktsarpalis
Copy link
Member

@eiriktsarpalis eiriktsarpalis commented Jun 29, 2023

This PR applies a number of improvements and bugfixes to JsonNode following up from #87381.

  • Avoid potential torn reads on lazy initialization in methods that attempt to access the JsonElement field directly in JsonObject and JsonArray, related to the fixes found in Ensure delayed initialization of JsonArray/JsonObject is thread-safe #77567. Makes adjustments so that code in the two classes is more uniform. Change contained in b02f735.
  • Simplify the implementation of JsonValue, more specifically this removes the obsolete JsonValueTrimmable/JsonValueNotTrimmable distinction in favor of a common resolver-agnostic implementation. Fixes a number of issues related to the newly added DeepEquals method when comparing JsonValue. Change contained in d9ac996.
  • Misc opportunistic simplifications, including simplifying the JsonNode serialization implementations.

cc @RaymondHuy

@ghost
Copy link

ghost commented Jun 29, 2023

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR applies a number of improvements and bugfixes to JsonNode following up from #87381.

  • Avoid potential torn reads on lazy initialization in methods that attempt to access the JsonElement field directly in JsonObject and JsonArray. Makes adjustments so that code in the two classes is more uniform. Change contained in b02f735.
  • Simplify the implementation of JsonValue, more specifically this removes the obsolete JsonValueTrimmable/JsonValueNotTrimmable distinction in favor of a common resolver-agnostic implementation. Fixes a number of issues related to the newly added DeepEquals method when comparing JsonValue. Change contained in d9ac996.
  • Misc opportunistic simplifications, including simplifying the JsonNode serialization implementations.
Author: eiriktsarpalis
Assignees: eiriktsarpalis
Labels:

area-System.Text.Json

Milestone: -

@@ -31,12 +31,21 @@ public void Add(JsonNode? item)
/// </summary>
public void Clear()
{
for (int i = 0; i < List.Count; i++)
List<JsonNode?>? list = _list;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updates the code so that List is not accidentally initialized here.

@@ -5,22 +5,14 @@ namespace System.Text.Json.Nodes
{
public abstract partial class JsonNode
{
// trimming-safe default JsonSerializerOptions instance used by JsonNode methods.
private protected static readonly JsonSerializerOptions s_defaultOptions = new();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer necessary since the JsonNode.WriteTo implementations no longer compose via intermediate JsonConverter.Write calls that require a JsonSerializerOptions.

InitializeIfRequired();
Debug.Assert(_dictionary != null);
_dictionary.Add(propertyName, value);
Dictionary.Add(propertyName, value);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaces initialization code with a Dictionary property that drives initialization, following the style of the JsonArray implementation.

@@ -220,16 +216,19 @@ internal override void GetPath(List<string> path, JsonNode? child)

internal void SetItem(string propertyName, JsonNode? value)
{
InitializeIfRequired();
Debug.Assert(_dictionary != null);
JsonNode? existing = _dictionary.SetValue(propertyName, value, () => value?.AssignParent(this));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to avoid a delegate allocation in the setter method.

{
if (_jsonElement.HasValue)
GetUnderlyingRepresentation(out JsonPropertyDictionary<JsonNode?>? dictionary, out JsonElement? jsonElement);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to avoid potential races with the intializer method that clears out the _jsonElement field.

@eiriktsarpalis eiriktsarpalis merged commit 1db4357 into dotnet:main Jul 1, 2023
98 of 103 checks passed
@eiriktsarpalis eiriktsarpalis deleted the cleanup-jsonvalue branch July 1, 2023 08:07
@Sergio0694
Copy link
Contributor

Sharing updated binary size numbers from the Store just for tracking:

Version Package size
8.0.0-preview.6.23316.2 65,121 KB
8.0.0-preview.7.23330.6 65,143 KB

Very small 20 KB regression (I suspect just generally for the new stuff added in #87381, especially I guess those new IEnumerable<T> APIs, given we have MCG generating WinRT wrappers left and right for those), but completely negligible in general. I know we have proper size tracking through unit tests and ASP.NET now, but given .NET Native is different and handles trimming completely different, I figured having more reference points for this can't hurt 😄

@eiriktsarpalis
Copy link
Member Author

especially I guess those new IEnumerable APIs

You mean JsonArray.GetValues<T>()? I'd be surprised this would be rooted unless it somehow ends up being invoked somewhere.

@Sergio0694
Copy link
Contributor

Yeah that's the one. I was thinking that might be another case of MCG being overzealous and preemptively rooting all generated CCWs for IEnumerable<T> types it finds in the assembly, just in case they were ever marshalled to the WinRT side. I believe this generation step runs before the IL-level trimming done by ILC, so it'd cause that to be rooted even though the method is never actually called (@jkoritzinsky correct me if I'm wrong 😄). Or, I suppose, that 40 KB could also just be a small difference just due to general code reshuffling with the recent changes somewhere. To be clear — it's not an issue, I was just trying to give a possible explanation for the (very small) binary size regression compared to Preview 6, is all 🙂

@Sergio0694
Copy link
Contributor

Some more updated numbers, looks like the latest few PRs did help a little bit again 😄

Version Package size
8.0.0-preview.7.23330.6 64,771 KB
8.0.0-preview.7.23360.15 64,713 KB

So that's another small (but free) ~60 KB improvement. Note that the baseline with the previous version doesn't match that from #88194 (comment), but that's only because I've rebased the dogfooding branch on top of our main in the meantime. Just consider the relative size delta between different System.Text.Json versions 🙂

@dotnet dotnet locked as resolved and limited conversation to collaborators Aug 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants