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

Add Try methods to JsonObject #111229

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Add Try methods to JsonObject #111229

wants to merge 7 commits into from

Conversation

Flu
Copy link

@Flu Flu commented Jan 9, 2025

As per the API proposed in #110244, the following functions have been added to JsonObject:

public bool TryAdd(string propertyName, JsonNode? value);
public bool TryAdd(string propertyName, JsonNode? value, out int index);
public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode, out int index);

This resolves #110244

@Flu Flu marked this pull request as draft January 9, 2025 10:02
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 9, 2025
@Flu
Copy link
Author

Flu commented Jan 9, 2025

@dotnet-policy-service agree

@Flu
Copy link
Author

Flu commented Jan 9, 2025

@eiriktsarpalis Sorry to trouble you, I would have written some tests, but at the moment I seem not to be able to run any. I followed the testing guide here: docs/workflow/testing/libraries/testing.md

Neither dotnet build /t:Test nor running the test inside Visual Studio works, it complains about missing the SDK for .net 10. Not sure how to navigate this problem, since the guide says that if you build the clr and the libs with build.cmd -subset clr+libs -rc Release, then it should work. The build itself finishes succesfully, but trying to run the tests fails. Any idea why? Also, prior to trying to run dotnet build /t:Test, I did cd into runtime\src\libraries\System.Text.Json\tests.

@huoyaoyuan
Copy link
Member

it complains about missing the SDK for .net 10

Don't use the global dotnet command, but the dotnet.cmd at repository root instead. It will resolves to a local copy of nightly SDK.

@Flu
Copy link
Author

Flu commented Jan 9, 2025

@huoyaoyuan then how can I debug in Visual Studio if I need to use dotnet.cmd?

Also, for example, if I needed to execute the tests in ./src/libraries/System.Text.Json/tests/System.Text.Json.Tests/, how would I go about doing that? I tried doing .\dotnet.cmd build /t:Test /p:XUnitOptions="-class System.Text.Json.Tests.JsonNode.ParseTests" but it seems to be executing other tests that are not there, so not sure what the proper usage would be here.

@eiriktsarpalis
Copy link
Member

@Flu You can use the -vs flag in build.cmd to launch a solution using the bootstrapping sdk in the environment:

build.cmd -vs src\libraries\System.Text.Json\System.Text.Json.sln

That should make it possible to run tests from the test explorer as normal.

@Flu
Copy link
Author

Flu commented Jan 10, 2025

@eiriktsarpalis I am now getting this error when trying to run the tests from inside visual studio after I've used your command:

MSB4018: The "Microsoft.DotNet.ApiCompat.Task.ValidateAssembliesTask" task failed unexpectedly.
System.MissingMethodException: Method not found: 'System.ReadOnlySpan`1<!0> System.Collections.Immutable.ImmutableArray`1.AsSpan()'

This has nothing to do with my changes, as it's happening on the main branch as well, most recent commit.

@eiriktsarpalis
Copy link
Member

@Flu did you try building the full runtime+libraries first?

build.cmd clr+libs -rc release

@Flu
Copy link
Author

Flu commented Jan 10, 2025

@eiriktsarpalis Yes. I mean, I am adding test.libs as well:

.\build.cmd -subset clr+libs+libs.tests -rc Release /p:RunApiCompat=false

This is the exact command I've been using. Removing the /p:RunApiCompat=false doesn't seem to be making a difference, tried without it too.

@eiriktsarpalis
Copy link
Member

And that step is building successfully?

@Flu
Copy link
Author

Flu commented Jan 10, 2025

Yes

@eiriktsarpalis
Copy link
Member

@ViktorHofer any ideas what might be missing?

@ViktorHofer
Copy link
Member

Yes, it looks like APICompat is currently broken on desktop msbuild (which is used inside VS): dotnet/sdk#45004

@eiriktsarpalis
Copy link
Member

You shouldn't need to disable ApiCompat to get your dev environment set up though. I would recommend stashing your changes, then build the repo, make sure that tests run from the IDE, and finally pop your changes back in.

@PranavSenthilnathan
Copy link
Member

I've been having this issue as well. The workaround that I use is to run the tests from VS test explorer but when the build fails and VS shows the failure dialog, select "Yes" to run tests from last successful build. Everything (e.g. debugging, breakpoints, etc.) works as expected since the API compat step of the build (which is component that is failing) seems to be after the product and test binaries are already generated.

@huoyaoyuan then how can I debug in Visual Studio if I need to use dotnet.cmd?

Also, for example, if I needed to execute the tests in ./src/libraries/System.Text.Json/tests/System.Text.Json.Tests/, how would I go about doing that? I tried doing .\dotnet.cmd build /t:Test /p:XUnitOptions="-class System.Text.Json.Tests.JsonNode.ParseTests" but it seems to be executing other tests that are not there, so not sure what the proper usage would be here.

D:\git\runtime\src\libraries\System.Text.Json\tests\System.Text.Json.Tests> dotnet test /p:xunitoptions="-method System.Text.Json.Tests.Utf8JsonWriterTests.WriteStringValueSegment_Utf8_Split8CodePointsBasic"

You should be able to use dotnet.cmd here as suggested above if this doesn't resolve to the right version.

@Flu Flu marked this pull request as ready for review January 11, 2025 02:51
@Flu
Copy link
Author

Flu commented Jan 11, 2025

@PranavSenthilnathan Thank you, it finally worked. Clever solution!

@Flu
Copy link
Author

Flu commented Jan 13, 2025

@eiriktsarpalis I'm ready for a review

@@ -34,6 +34,43 @@ public void Add(string propertyName, JsonNode? value)
value?.AssignParent(this);
}

/// <summary>
/// Adds an element with the provided name and value to the <see cref="JsonObject"/>, if a property named <paramref name="propertyName"/> doesn't already exist..
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Adds an element with the provided name and value to the <see cref="JsonObject"/>, if a property named <paramref name="propertyName"/> doesn't already exist..
/// Adds an element with the provided name and value to the <see cref="JsonObject"/>, if a property named <paramref name="propertyName"/> doesn't already exist.

ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
}

var success = Dictionary.TryAdd(propertyName, value, out index);
Copy link
Member

Choose a reason for hiding this comment

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

This is probably failing to build in .NET 9 since that version of OrderedDictionary doesn't have the overload that exposes the index. I would recommend emulating the behavior using a second lookup via IndexOf in .NET 9 targets.

@@ -62,12 +62,16 @@ public bool TryAdd(string propertyName, JsonNode? value, out int index)
ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
}

#if NET10_0_OR_GREATER
Copy link
Member

Choose a reason for hiding this comment

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

We only need this for .NET 9. Earlier targets polyfill the current OrderedDictionary implementation.

Suggested change
#if NET10_0_OR_GREATER
#if NET9_0

Copy link
Author

Choose a reason for hiding this comment

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

I assume it becomes this then?

#if NET9_0
            var success = Dictionary.TryAdd(propertyName, value);
            index = Dictionary.IndexOf(propertyName);
#else
            var success = Dictionary.TryAdd(propertyName, value, out index);
#endif

Copy link
Member

Choose a reason for hiding this comment

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

Right.

Comment on lines +68 to +69
var success = Dictionary.TryAdd(propertyName, value);
index = Dictionary.IndexOf(propertyName);

Choose a reason for hiding this comment

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

Suggested change
var success = Dictionary.TryAdd(propertyName, value);
index = Dictionary.IndexOf(propertyName);
var success = Dictionary.TryAdd(propertyName, value);
index = success ? Dictionary.Count : Dictionary.IndexOf(propertyName);

Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be Count - 1? Also, this assumes that OD appends new entries at the end, but what if that changes in the future?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it should be Count - 1.
Even though I don't think it's likely for that to change (it's by definition ordered the way elements are added), the fix to not using Count - 1 is to just leave the code like it is now, right?

Choose a reason for hiding this comment

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

Yes that should've been Count - 1.

Also, this assumes that OD appends new entries at the end, but what if that changes in the future?

Hmm, independent of however it's currently implemented, what invariant is Ordered supposed to guarantee?

An alternative is to do IndexOf first and if it doesn't exist then do d.Insert(d.Count, key, value) (note: not Count - 1) which would add at the end.

Not optimizing this case is also fine.

Copy link
Member

Choose a reason for hiding this comment

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

To be clear, I don't think that it would ever change; I'm just pointing out that it's depending on an invariant from a separate component.

what invariant is Ordered supposed to guarantee?

It's a dictionary that also implements a deterministic IList with $\mathcal O(1)$ access.

Copy link
Author

Choose a reason for hiding this comment

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

So what is the consensus here? Do I leave it as it is?

Copy link
Member

@eiriktsarpalis eiriktsarpalis Jan 14, 2025

Choose a reason for hiding this comment

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

I'm fine to apply the optimisation provided there is test coverage, e.g. a test that verifies that the returned index is correct in that scenario.

Copy link
Member

Choose a reason for hiding this comment

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

A comment above the line explaining the arrangement might help as well.

Comment on lines +151 to 152
index = Dictionary.IndexOf(propertyName);
return Dictionary.TryGetValue(propertyName, out jsonNode);

Choose a reason for hiding this comment

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

Similar to above, the TryGetValue can be skipped if IndexOf returns false.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, this one can be improved:

index = Dictionary.IndexOf(propertyName);
if (index == -1)
{
    return Dictionary.TryGetValue(propertyName, out jsonNode);
}

jsonNode = null;
return false;

Something like this for the .NET 9 part

Copy link
Member

Choose a reason for hiding this comment

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

Once you have the index you don't need to perform another key-based lookup:

index = Dictionary.IndexOf(propertyName);
if (index < 0)
{
    jsonNode = null;
    return false;
}

jsonNode = Dictionary.GetAt(index);
return true;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Text.Json community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a JsonObject.TryAdd method.
5 participants