Open DL444 opened 1 month ago
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis See info in area-owners.md if you want to be subscribed.
The problem also reproduces when targeting .NET 9 RC 1 with System.Text.Json 9.0.0-rc.1.24431.7.
Supporting JsonExtensionData init
properties would require some rework of the extension data implementation, since it currently assumes that it can populate the property lazily. This can be observed if you run the following code using the reflection-based serializer which ignores init
properties:
var result = JsonSerializer.Deserialize<Demo>("{\"Value\":42}");
Console.WriteLine(result.ExtensionData is null); // True
result = JsonSerializer.Deserialize<Demo>("{\"Value\":42,\"OverflowValue\":\"42\"}");
Console.WriteLine(result.ExtensionData is null); // False
This behaviour is not trivial to replicate in the source generator which cannot ignore init
semantics. It might be safer if we just decide that this pattern is not supported and emit a better error message. The obvious workaround is making the property settable.
I was just reading on UnsafeAccessorAttribute introduced in .NET 8 and I wonder if it can be used to solve the problem by ignoring the init
semantics in a reflection-free way. I cobbled up the following code snippet and it seems to be working fine:
Demo demo = new();
SetDemoExtensionData(demo, []);
Console.WriteLine(demo.ExtensionData is not null); // Prints "True".
static void SetDemoExtensionData(Demo demo, Dictionary<string, JsonElement>? value)
{
ref Dictionary<string, JsonElement>? reference = ref GetExtensionDataReference(demo);
reference = value;
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<ExtensionData>k__BackingField")]
extern static ref Dictionary<string, JsonElement>? GetExtensionDataReference(Demo demo);
}
public sealed class Demo
{
public int Value { get; init; }
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; init; }
}
I imagine the source generator can generate the SetDemoExtensionData
method in the middle and use it to set ExtensionData
lazily. Not sure if there are any problems that preclude this solution. Some coming to mind:
extern static
method cannot be generic and must be generated per type.init
accessors with custom logic can be a blocker, but I'm not convinced that this is a must-support scenario.required
overflow property still requires an eager implementation, but required overflow properties sounds questionable.Unsafe accessors are a useful tool, however System.Text.Json needs to be able to support netstandard2.0 which doesn't have those APIs. We could of course special case newer targets so that they use the feature, however that creates a degree of bifurcation that wouldn't be practical to maintain.
Description
Source generated JSON deserialization fails with
JsonExtensionData
property usinginit
accessor:Reproduction Steps
Expected behavior
Application completes with no significant problems and prints "True".
Actual behavior
Application throws exception:
Regression?
Probably not:
init
property deserialization is not supported by System.Text.Json in previous .NET versions.Known Workarounds
Change the overflow property to use a public or internal set accessor.
Configuration
.NET info:
Project configuration:
Other information
No response