-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Should we add support to ignore cycles on serialization? #40099
Comments
From #30820 (comment):
|
From #30820 (comment)
|
From #30820 (comment)
|
@jozkee is this required to ship 5.0 (per milestone set here)? |
@danmosemsft I think it could disappoint many users if we don't ship this on 5.0. |
@jozkee @ericstj FWIW when I heard that STJ would have loop handling in 5.0, I just assumed that would include something along the lines of Ignore. It would be disappointing to not have something along those lines in 5.0 |
What's the verdict? Is this going to ship in .net 5? |
As per the milestone this was set to 6.0.0. The feedback around the importance of this came in after feature complete for 5.0 and didn't meet the bar. We will aim to add support here in 6.0. |
@ericstj - even though I can understand it's difficult to prioritize - everyone of course wants to have their piece in every release (which is not possible if wanting to meet the deadlines). I do want to set the record straight though, because unless I'm missing something
doesn't ring very true for me. First off, I'll assume everybody here knows about the significant outcry that came after releasing 3.0 a year ago about not having feature parity and easy compatibility on this critical feature (JSON handling). Note that it's not only about feature parity, but also about compatibility. Like expressed later, it's simply not fully complete to do a different solution with significant drawbacks, causing a lot of rework for anyone with a web app sending JSON relying on EF with any self-referencing loops anywhere (should be one of the most common applications out there with .NET Core?). Ok, so it was quite clear from the beginning - but of course, feedback about the specific solution didn't come from the start (it didn't exist, so not possible). According to your releases, you reached "feature complete" with Preview 8, released on Aug 25th (https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-8/). You were not "feature complete" (almost, but not fully), with Preview 7 (see first section of https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-7/) on Jul 21st. Ok. Jozkee seems to have announced his plan/proposal on May 5th (#30820 (comment)). The first comment about ignore came 6 days later on May 11th (#30820 (comment)). No reply. Then after that another person commented on Jul 14th quite strongly and imo clearly the issue (#30820 (comment)). No reply. Then I have a try at it in perhaps a more structured approach on Jul 27th (#30820 (comment)), which finally ends up with this issue right here after a quick back-n-forth with Jozkee. So, unless I'm very much missing something, it's simply not true that
To quote diegogarber (again, from Jul 14th)
To me, that's pretty clear. If you want to push it to 6.0.0 at this point in time I understand (since we're so close to release). I understand it's challenging to do such great work in an open environment with the community involved. But please, do not blame the lateness of the feedback from the community. |
Sorry @cjblomqvist I didn't mean to blame anyone here. I was stating the facts about the decision made in this issue with its specific dates. On July 15 we branched dotnet/runtime and started stabilizing for Preview8. Note that it's not like this aspect of the feature wasn't discussed in the design review. My understanding was that there was push back in the design review against the option of letting callers of the serialization API silently drop members from types which they don't own through the cycle-ignore option. @jozkee calls this out at the top of this issue and it's something that will likely still need to be discussed when bringing this change in to 6.0. We are hearing the community that it's important to reconsider this decision and will get to it. We don't want to rush that decision. The new API needs to be designed and approved. Even once this is approved it will need testing to ensure it meets folks' needs, provides adequate performance, and meets security guarantees. We don't like to rush this sort of thing due to the high-compatibility bar we hold for components in dotnet/runtime, thus our early lock-downs. I truly apologize that this new 5.0 feature is not meeting your needs, and I hope we can improve it in 6.0. |
@ericstj thanks for keeping the dialog open.
Thanks!! |
Thanks for your reply @ericstj - communication is definitely important and something that easily gets wrong in written form over the Internet. I do understand consideration needs to be taken, and it's good you added insight about the branching point for Preview 8 in July 15th. I guess the overall negativity on this stems from the overall disappointment of not being able to migrate to this for 2 years (3.0 -> 6.0), for what looks like (probably wrongly) a quite small feature. Isn't it ironic that one of the major hurdles for getting this implemented is the implications of needing to keep backwards compatibility on this, when basically the whole issue stems from exactly backwards compatibility with Newtonsoft.... :) |
Quote from #30820 (comment)
I just want to throw in my two cents on this one. I decided to go down the route of porting my application from Anyways, the solution I had to come up with was to use In short, come on, pushing Please consider adding this to a minor update to 5.0 instead of making us all wait another year to be able to use this new JSON serializer!! Please... |
I'm sorry but I just straight up disagree with this. @jozkee highlighted in his initial post on #30820 a perfectly valid use case for Discussion ensued in that issue for a while, and for some reason at some point it was decided that ingore wasn't required, despite many community contributions specifically requesting it. Eventually I too commented (#30820 (comment)) requesting it, and that was in May. Many of the comments above are responses which came after then. There was clear issue with the lack of Ignore for months, and multiple people in the community have been unable to experience the improvements that come with STJ because of this catastrophically broken part of the "mostly drop in replacement" for Newtonsoft which requires either introducing a load of additional parsing (if using Preserve and all the extra stuff that comes with it). I've been asked numerous times what i think about STJ and I've had to say that although overall it is a significant improvement over Newtonsoft, if there's any reliance on newtonsoft's Ignore then it's totally unusable, so the only real option is to nerf the applications which would otherwise use it and use newtonsoft instead. @cjblomqvist's comment from July (which was already copied above) is an excellent rationale for why we needed this. Realistically, if .NET 5 is "the future", shooting everyone who uses Ranting aside, I understand the rationale for not having it, as maybe it shouldn't have been there in the first place. Unfortunately that error was made many years ago, and in the interests of backwards compatibility the code which enables aome "bad" practices should be in place. That said, in what is probably most instances where this is a problem (where people are serialising EF objects), the "lost" data isn't relevant, as it's usually a child referring to its parent anyway, which referred to its child in the first place. |
A bit more on schedule so folks understand where we are at now. We switched gears from feature development to bug-fixing around mid-July when we branched for Preview8. At that point it was all-hands on deck to get bugs fixed and bring 5.0 up to ship quality. We haven't been doing feature work since then, and new APIs and design changes are feature work (more on that below). We're now pretty much done with bug-fixing for 5.0 and have a chance to start looking at features again.
System.Text.Json does not aim to be compatible with Newtonsoft.Json. We're specifically trying to be different to provide a different value proposition. Those differences are actually what drives folks to consider using System.Text.Json instead. We are trying to make System.Text.Json work for as many folks as possible, but ease of adoption is different than compatibility. I don't see a backwards compatibility problem with adding this feature, it should be behind an opt-in flag. I think the main hurdle for this feature was/is design principles.
I respectfully disagree. I see plenty of discussion from @jozkee @ahsonkhan @steveharter and @JamesNK that acknowledges the scenario. It was also discussed in the API review. There was a good understanding of ReferenceLoopHandling.Ignore and what it's purpose was. There's no conspiracy to suppress this feature. It was an intentional design decision. Sometimes those are even harder to undo, since you need to revisit a decision that was already made, but that is exactly what we're doing here. Please help us make the right call this time around. If it were me trying to present the case for this feature I'd share things like:
I bet this type of data would help the API reviewers better understand the tradeoffs when making this decision. I am not asking the community to provide all this, but just sharing some suggestions for how this issue should proceed. Next steps are an API proposal and a review. Assuming that goes through, the feature should be implemented in the main branch codebase (6.0). Once that's done, we can talk about porting it to the servicing branch consumable via the package, once we see what the final design looks like and understand the risk. I can make no promises as we typically do not permit API additions in servicing, but since this also ships as a NuGet package we can technically make it work. I suspect the viability of a servicing release here depends on risk. Thanks everyone. |
Thanks Eric for your input. I just want to clarify that I wasn't just railing against the project/maintainers/contributors, but was trying to establish the view for me (and those who I've spoke to in favour of I personally am not even a fan of the current implementation that exists in Json.NET. While I do make use of it, and agree with @JamesNK that it definitely shouldn't be the default because it could in theory cause data to be lost, I also think that there is some way of implementing behaviour similar to what the other implementation did, while maybe doing it more "safely". I wouldn't want to be the one putting forward the case for the API myself because although I have a few years under my belt, I think things like this might be a bit above me. I think the core issue with the There are elements of the There are a couple of rough ideas off the top of my head for approaches to this.
I think 2 is the best option because while I think that there is usually no reason to have something like this, for the example @cjblomqvist had, where it's for display purposes only and the data isn't expected to be saved back anywhere after deserialisation, it's not the worst plan. I think the user can understand that in the ignore example on the original #30820 (comment), Angela would obviously be a subordinate to her manager. I suppose as always, it depends, as in that scenario it's clear from context. I think Hope that all makes sense. :) |
The main scenario as I understand:
The main question I have is whether the POCO types are general-purpose meaning used for both the scenario above as well as for other data-interchange scenarios. I assume the POCOs are general-purpose based on the feedback that So as a potential work-around, would using DTOs specific for this scenario (that would not have the properties that should be ignored) work? One could argue that using DTOs in this manner is a best practice. The counter-argument is that this is cumbersome and\or the existing Newtonsoft semantics are perfectly fine. Assuming DTOs are not the answer, for 6.0 I believe we can tweak the design of Also, for 6.0, STJ should consider addressing the deterministic issues that have a workaround in Newtonsoft. See #1085. |
namespace System.Text.Json.Serialization
{
public partial class ReferenceHandler
{
// Existing:
// public static ReferenceHandler Preserve { get; }
public static ReferenceHandler IgnoreCycle { get; }
}
} |
@terrajobst FWIW here's our usecase for this functionality, just because there was a number of questions about this on the video: "So we have a central structured logging service where users can send arbitrary C# objects, and then our service writes them to several targets (Elasticsearch, APM, etc) and these targets often want the payload to be JSON. So we need to be able to transform arbitrary user-created objects whose structure is out of our control into JSON. It is not uncommon at all for these objects to contain circular references (e.g. due to Entity Framework). In order to prevent exceptions when serializing these arbitrary user-created objects to JSON, we use Ignore with Newtonsoft and hence have not been able to switch these services to STJ. In this use case, the inability of Ignore to round-trip (which has been mentioned upthread as a reason to not offer this behavior) is a complete non-issue. We just need to be able to serialize the object into something human-readable in all cases without the serialization blowing up, even if there is some information loss - because the end consumer of the JSON is human eyeballs in Elastic/APM/etc. It's better for us to get some information to the user in the cases where they passed circular objects, than having to drop the user's records entirely." |
This feature seems specific to a handful of related but somewhat different scenarios. It may be safer and more flexible to improve the existing extensibility model to support ignore and provide various samples around that.
I was under the assumption that the behavior could be non-deterministic in some cases, but that doesn't appear to be the case at least with Newtonsoft. Ignore only applies to the exact moment a cycle is detected. Thus:
with the result being a cyclic graph is converted into a noncyclic graph, assuming a root of course. Here's a Newtonsoft test of a A, B and C nodes where they all reference each other with A being the root. static void Main(string[] args)
{
// Uncommenting the line below changes reflection order.
// typeof(Node).GetProperty("Ref2");
var a = new Node { Name = "a" };
var b = new Node { Name = "b" };
var c = new Node { Name = "c" };
a.Ref1 = b;
a.Ref2 = c;
b.Ref1 = a;
b.Ref2 = c;
c.Ref1 = a;
c.Ref2 = b;
string json = JsonConvert.SerializeObject(a, Formatting.Indented, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
});
Console.WriteLine(json);
}
}
public class Node
{
public string Name { get; set; }
public Node Ref1 { get; set; }
public Node Ref2 { get; set; }
} Output:
B and C are serialized twice, but the root A is only serialized once. So B->A and C->A are ignored since in those cases the "child" node A was already serialized as a parent. If the reflection order changes (uncomment the line that mentions this) the data is still the same, just in different ordering:
|
I assume the design will cover:
|
Sending back to 1. IgnoreCycle: Original proposal. |
What about something like
|
@ericsampson sounds like a reasonable name that exactly describes what the API does. |
Changed to plural via email: namespace System.Text.Json.Serialization
{
public partial class ReferenceHandler
{
// Existing:
// public static ReferenceHandler Preserve { get; }
public static ReferenceHandler IgnoreCycles { get; }
}
} |
Background and Motivation
Even though we have covered the blocking issue of not being able to (de)serialize reference loops (#30820, #29900) by adding
ReferenceHandler.Preserve
to S.T.Json, there is many asks for adding an option equivalent to Json.NET'sReferenceLoopHandling.Ignore
.Motivations for doing this are:
ReferenceHandler.Preserve
may be too cumbersome and increases payload size.ReferenceHandler.Preserve
creates JSON incomprehensible by serializers other than S.T.Json and Json.NET.Proposed API
namespace System.Text.Json.Serialization { public abstract partial class ReferenceHandler { public static ReferenceHandler Preserve { get; } + public static ReferenceHandler IgnoreCycle { get; } } }
Usage Examples
Alternative Designs
This new API is being added to
ReferenceHandler
class since this can be considered as an alternative to deal with references that is more isolated to the circularity problem during serialization.Comparison with Newtonsoft.Json
Comparison with existing ReferenceHandler.Preserve setting in S.T.Json
Risks
One downside is that users are uanble to implement their own
ReferenceHandler
and cannot make their own "Ignore Cycle" handler. The discrimination between preserve and ignore would occur with an internal flag.Concerns of adding this feature (users must be aware of these problems when opting-in for it):
The text was updated successfully, but these errors were encountered: