-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
What changed in .NET 6 that would affect JsonElement Equals behavior? #62585
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json Issue DetailsI wasn't really sure what category to use for the issue type or whether this is even the right place to bring it up, but I checked the breaking changes list for .NET 6 and I don't see any mention of changing equality checking behavior for I'm not certain whether this is specifically an issue for comparing var jsonElement = JsonSerializer.Deserialize<JsonElement>("true");
var cloned = jsonElement.Clone();
jsonElement.Equals(cloned).Should().BeTrue(); When debugging this, I found that in if (!thisResult.Equals(thatResult))
{
// This path is entered on .NET 6
return false;
}
|
|
That makes sense, I was trying to find out what was causing it to change all of a sudden. Thanks for the response! |
How is the user supposed to figure that out? Just from |
I think it is; the methods are there and documented and callable and return a value. Just because the struct doesn't override the method doesn't mean the method isn't still part of the contract. Why haven't we overridden it to return something meaningful? |
@svick @stephentoub yes my initial assumption was that it's a struct, so if I clone it, it'll have the same values and thus be equal to its' source. |
I'm not sure if this was considered back when the types were being designed, @bartonjs might be able to comment on that.
True, but my impression is that all structs that define a meaningful concept of equality must also implement |
To demonstrate why this is not a regression, consider the following example: using System;
using System.Text.Json;
var jsonElement = JsonDocument.Parse("true").RootElement;
var cloned = jsonElement.Clone();
Console.WriteLine(jsonElement.Equals(cloned)); The above prints |
Are there no scenarios where it produced the "right" answer in .NET 5? Just because it doesn't always produce the right answer doesn't mean there aren't scenarios where it did and where devs could have taken a dependency on it. Regardless, it's very strange that a struct's Equal would be invalid. Are there other cases of that in our core libraries, where a public struct's equality doesn't provide some definition of equality? |
I guess it depends on what the perceived definition of equality is. There more I look into this the more it seems that the answer is "wrong" for most useful applications: var jsonElement = JsonDocument.Parse("[true,true]").RootElement;
Console.WriteLine(jsonElement[0].Equals(jsonElement[1])); // False
var jsonElement1 = JsonDocument.Parse("{}").RootElement;
var jsonElement2 = JsonDocument.Parse("{}").RootElement;
Console.WriteLine(jsonElement1.Equals(jsonElement2)); // False when run in both .NET 5 and .NET 6.
A quick reflection-based query seems to indicate we have ~200 public structs like that only in System.Private.CoreLib: typeof(int).Assembly.GetTypes()
.Where(t => t.IsPublic && t.IsValueType)
.Where(t => t.GetMethod("Equals", new Type[] { typeof(object) }).DeclaringType != t)
.Count() |
First, 142 of those 202 results are enums. Second, most of the remaining ones are in InteropServices and CompilerServices. And of the ones that aren't, like struct enumerator types, there's little-to-no expectation that comparison of those would be meaningful, there's little-to-no reason to try to store them in a dictionary, etc. Third, just because a struct doesn't override Equals doesn't mean its Equals is wrong; the base implementation can be doing the 100% correct thing, albeit more slowly in some cases than would be ideal. System.Reflection.InterfaceMapping's Equals will do the "right thing", just comparing each of the fields. KeyValuePair's Equals will do the "right thing", just comparing each of the fields. Etc. JsonDocument, JsonElement, as a user, I fully expect to be able to check those for equality, they provide Equals, there's no documentation about Equals not doing the "right thing", there's no attribution on them that will produce a compilation warning suggesting I shouldn't be using them, etc. Regardless of whether this is classified as a regression or a breaking change, there's an issue here, IMHO. |
We could consider adding equality (and implement There are types for which equality simply does not make sense; the fact that every type inherits an |
I think semantic equality is more involved and I wouldn't expect that from a Does a |
That would violate the requirement that |
Technically though the argument passed in the |
@eiriktsarpalis Even if you interpret it that way, it would still require the following code to print JsonElement element = ...;
object box = element;
Console.WriteLine(box.Equals(box)); |
Fair enough. We could consider implementing byte-for-byte equality equality comparison in the future although that is bound to disappoint users expecting a different brand of document equality. I feel that changing nothing at all is a valid approach too. |
I would love to have meaningful using System.Text.Json;
var json = "{\"value\": 123}";
var dict1 = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
var dict2 = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
var value1 = dict1["value"];
var value2 = dict2["value"];
Console.WriteLine(value1.Equals(value2)); // False This is really strange that the same JSON deserialized by the same deserializer gives values that are not equal. I also heard that you should always override |
How about providing a As it currently stands |
@scharnyw it's a valid approach but needs design/experimentation and implementation. would you be willing to contribute? I'm not quite sure about the original design if this was intentionally left out or missed out by accident. I don't want to give anyone false hopes we will pick this up anytime soon given we have plenty of issues in this area and relatively little people working on it... |
@krwq I may do some experimentation when I have time available and if this issue remains unresolved. But since I have next-to-no knowledge of the internal workings of STJ I don't think I'll be a good candidate for contribution. |
As with any feature even if you know some internals like us we'll still need to learn more of them if we want to provide some good design/implementation since most of the people don't know every bit of code - our code base is rather large overall and usually we know only code we touched or reviewed. It's usually good to start with public APIs and review what will likely need changes and then that should immediately give you idea on what are the corner cases. Then you do some prototyping - as much testing as it makes sense at this stage, then API review/design review if needed and after that final implementation and possibly reiteration. Here I'd personally review current behavior and most likely make breaking change if current implementation is mostly useless and document it. It will likely need API review for recommendation if they prefer breaking change or new APIs - some possible design could be provided here but also add recommendation that breaking change might be better option. As you see that requires a bit of time and given all issues we can't fix them all so we will need to pick based on cost/value of given API or change. It's not that we don't want to fix this issue - we do - we just have other issues which are likely higher priority than this. Usually we go through list of issues in the beginning of the release (or very end of the previous one) which would be soon from now and decide based on that. My initial thought is that this one likely won't meet the bar yet (unless there is a series of similar issues and we would tackle all of them) and therefore we ask for help here. Hope that helps. |
Closing as duplicate of #33388. We can debate there whether we want |
I wasn't really sure what category to use for the issue type or whether this is even the right place to bring it up, but I checked the breaking changes list for .NET 6 and I don't see any mention of changing equality checking behavior for
JsonElement
.I'm not certain whether this is specifically an issue for comparing
System.Text.Json.JsonElement
objects, or whether it's something deeper, but essentially the following test passes on .NET 5 and fails for me on .NET 6:When debugging this, I found that in
ValueType.cs
, the following check evaluates totrue
in .NET 5 andfalse
in .NET 6.thisResult
andthatResult
areJsonDocument
instances.The text was updated successfully, but these errors were encountered: