-
Notifications
You must be signed in to change notification settings - Fork 10.1k
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
JsonSerializerOptions constructor is not copying the context #38720
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json Issue Detailssince the JsonSerializerOptions doesn't copy the _context from the old to the new, the json source generator stuff will not work with mvc output formatter. the call that wants to make a copy is below. since Encoder is null, it tries to make a new JsonSerialzierOptions and this constructor ignores _context.
|
This behavior is by-design: the copy constructor does not copy the type/property metadata classes to avoid unnecessary references that potentially hinder garbage collection on the source options (e.g. a group of types are deserialized with source options, but never with destination options). Note that this characteristic was discussed during the initial implementation of the copy ctor (dotnet/runtime#30445, dotnet/runtime#30445). At that time the metadata was generated at runtime with reflection, but now with source-gen it is generated at compile-time. The GC concern applies in both cases. For your scenario, you would want to manually add your context to the new options instance. Is this feasible in your scenario? internal static SystemTextJsonOutputFormatter CreateFormatter(JsonOptions jsonOptions)
{
var jsonSerializerOptions = jsonOptions.JsonSerializerOptions;
if (jsonSerializerOptions.Encoder is null)
{
// If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters.
jsonSerializerOptions = new JsonSerializerOptions(jsonSerializerOptions)
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
jsonSerializerOptions.AddContext<MyContext>();
}
return new SystemTextJsonOutputFormatter(jsonSerializerOptions);
} |
This issue has been marked |
i copied that code from your code :) that's asp.net core code. the problem is your source gen will never be used by MVC if you do not copy the context or supply a Encoder. |
FYI @pranavkm -- should this bug be moved to dotnet/aspnetcore? aspnetcore/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs Lines 33 to 47 in a450cb6
|
Thanks for contacting us. We're moving this issue to the |
@halter73 @AnQueth thanks for the clarification. One approach given existing constraints is that if (de)serialization has not occurred on the user's options instance, then the If not, then perhaps we can look into new API to clone serialization metadata from one options instance to another. I'd advocate for separate API rather than inlining the logic in the copy ctor so that the choice to share references to metadata caches across options instances would be explicit (cf. #38720 (comment)). cc @eiriktsarpalis @steveharter Another approach could be for ASP.NET to expose API to receive a callback that adds a context to an options instance. |
As you say, I don't feel comfortable taking that change after we've gone out of our way not to mutate the user-provided options for so long.
What's the harm in sharing references to large metadata caches? I get that they're big, but I don't think there's much of a risk of applications cloning options objects and then retaining a reference to the clone longer than necessary. Why would anyone want to clone the options without the JsonSerializerContext if there is one?
I'm not sure I understand this completely, but new API doesn't fix existing apps that should be benefitting from the source generator. Based on my current understanding of the issue, I think the copy ctor should copy the JsonSerializerContext reference and I think we should take this as a patch in .NET 6. |
I think the copy constructor should try to copy the context as well, however at this point I'm slightly concerned about the potential for breaking changes by changing the current behavior. There are a few places in the current implementations where we will throw an exception if an options instance is detected to already contain a context: and I'm not entirely certain what the safest approach would be here, but we might want to expose a |
Bringing this back up - the only reason we use the copy constructor is to use the relaxed encoding which produces terser JSON for non-ASCII characters. Newtonsoft.Json uses a relaxed encoding by default and we'd wanted to keep parity with it when we started off. But given minimal endpoints do not do this, perhaps we can consider changing this behavior for 7.0 and not use the relaxed encoding with MVC. Note that we'd continue having to use the copy-constructor with the STJ-based IJsonHelper - https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.ViewFeatures/src/Rendering/SystemTextJsonHelper.cs#L36-L39, but that gets a lot less use. Perhaps it might suffice to provide a warning there if we know a JsonSerializerContext is associated with the JsonSerializerOptions. It might also be worthwhile consider patching this in 6.0 where we do not call the copy constructor if an AppContext switch is present. The current behavior is really buggy and silently limits the usage of the source generator in MVC apps. |
I wouldn't call it "limit" when the serialized output depends on how the endpoint is defined as the trivial example in the above bug shows. |
I can confirm that it works properly if setting the Encoder before setting the context:
|
Given the impact of this behavior, we should definitely provide a mechanism for copying context metadata from one options instance to another. We could do it in the copy ctor, but we'd have to be careful about breaking changes as stated in #38720 (comment). I'll spend some time prototyping this fix and circle back here. On a side note, @pranavkm is the actual (de)serialization done with an overload of the serializer that takes a |
@layomia here are the call sites :
Generally so, but #39628 points to an interesting scenario where source generator uses different settings than JsonSerializerOptions. Either way, it'd be unfortunate if people do the work to enable the source generator but don't end up benefiting from it because of MVC. |
Could the documentation and configuration of the
|
What references |
Nothing but ourselves it would seem... |
By default, STJ encodes most non-ASCII characters which is different from Newtonsoft.Json's defaults. When we first defaulted to STJ in 3.1, MVC attempted to minimize this difference by using a more compatible (unsafe-relaxed) encoding scheme if a user hadn't explicitly configured one via JsonOptions. As noted in dotnet#38720, this causes issues if a JsonSerializerContext is configured. This PR changes the output formatter to no longer change the JavaScriptEncoder. Users can manually configure the unsafe-relaxed encoding globally if they understand the consequences of doing so. Contributes to dotnet#38720
since the JsonSerializerOptions doesn't copy the _context from the old to the new, the json source generator stuff will not work with mvc output formatter. the call that wants to make a copy is below. since Encoder is null, it tries to make a new JsonSerialzierOptions and this constructor ignores _context.
The text was updated successfully, but these errors were encountered: