-
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
Add JsonIncludeAttribute
/JsonConstructorAttribute
support for private/inaccessible members in the source generator
#88519
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis Issue DetailsThe change in #88452 added internal/private member support for cc @jkotas
|
We should simply not support this feature with source generators for older targets. Reflection-based callback in source generators for older targets would violate promise of System.Text.Json source generator producing fast code that is trimming and AOT friendly. |
I'm happy to see this enhancement is officially endorsed by the team, I hope to see it for .NET9/10. While studying this limitation, I made a strange discovery and found out that putting a serializer context partial class within the class to be serialized (see the snippet below) allows the source generator to produce code that is indeed accessing the private fields, but still the compilation produces warnings and the serialization throws at runtime. I don't know if this can cause some concern today, but I am sure it will be properly replaced with the visibility suppression mechanism in the future. public partial class Vehicle
{
[JsonInclude, JsonPropertyName(nameof(Brand))]
private string? _Brand;
[JsonIgnore]
public string? Brand => _Brand;
[JsonSerializable(typeof(Vehicle))]
internal partial class VehicleSerializerContext : JsonSerializerContext;
}
/* The following code is produced by the generator, and obviously compiles correctly
* Still it has the warning "warning SYSLIB1038: The member 'Vehicle._Model' has been
* annotated with the JsonIncludeAttribute but is not visible to the source generator"
* and throws at runtime
*
* Getter = static obj => ((global::ConsoleApp1.Vehicle)obj)._Model,
* Setter = static (obj, value) => ((global::ConsoleApp1.Vehicle)obj)._Model = value!,
*/ |
This is expected behavior. It's not that (opted in) private members aren't supported in general, the more accurate statement is that members inaccessible to the generated class are not supported. Per C# semantics private members are accessible to nested types, so in this (relatively rare) case the source generator happens to work.
I can't reproduce in .NET 8. Is the project using multi-targeting perhaps? |
No. I can reproduce in .NET 8 (I'm using latest stable VS 17.9.6) with the following steps:
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ConsoleApp1
{
[JsonDerivedType(typeof(MotorVehicle), "MotorVehicle")]
[JsonDerivedType(typeof(Car), "Car")]
public partial class Vehicle
{
[JsonInclude, JsonPropertyName(nameof(Model))]
private string? _Model;
[JsonIgnore]
public string? Model => _Model;
[JsonSerializable(typeof(Vehicle))]
internal partial class VehicleSerializerContext : JsonSerializerContext;
}
public partial class MotorVehicle : Vehicle
{
[JsonInclude]
[JsonPropertyName(nameof(TransmissionType))]
[JsonConverter(typeof(JsonStringEnumConverter<TransmissionType>))]
private TransmissionType _TransmissionType;
[JsonIgnore]
public TransmissionType TransmissionType => _TransmissionType;
[JsonSerializable(typeof(MotorVehicle))]
internal partial class MotorVehicleSerializerContext : JsonSerializerContext;
}
public partial class Car : MotorVehicle
{
[JsonInclude]
[JsonPropertyName(nameof(CarClassification))]
[JsonConverter(typeof(JsonStringEnumConverter<CarClassification>))]
private CarClassification _CarClassification;
[JsonIgnore]
public CarClassification CarClassification => _CarClassification;
[JsonSerializable(typeof(Car))]
internal partial class CarSerializerContext : JsonSerializerContext;
}
public enum CarClassification
{
Unknown = 0,
Small,
Medium,
Large,
}
public enum TransmissionType
{
Unknown = 0,
Manual,
Automatic
}
class Program
{
public static int Main(string[] _)
{
var json = JsonSerializer.Serialize(new Car(), Vehicle.VehicleSerializerContext.Default.Vehicle);
Console.WriteLine(json);
var result = JsonSerializer.Deserialize(json, Vehicle.VehicleSerializerContext.Default.Vehicle);
Console.WriteLine($"result is {result!.GetType()}");
return 0;
}
}
} The compilation produces these warnings:
And the exception is:
|
I've been able to trim down to the following repro: string json = JsonSerializer.Serialize(new Derived(), Base.Context.Default.Derived);
Console.WriteLine(json);
public partial class Base
{
[JsonSerializable(typeof(Derived))]
internal partial class Context : JsonSerializerContext;
}
public class Derived : Base
{
[JsonInclude]
private int Value;
} The relevant code in the source generator uses the runtime/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs Lines 1373 to 1382 in 56d7e5d
Perhaps that method is hitting a corner case? cc @dotnet/roslyn That being said, it seems like a super low-priority issue with a clear workaround. |
As said, we were trying to exploit the limits of the system, and I came up with this idea of the inner class context. The workaround (making the fields internal) is acceptable for now but as you understand this lowers the quality of the encapsulation in my code and that's why I'm happy serialization of private fields will come anyway in a more general form, at some point.
FYI, it looks like the cc failed. |
Nah, github simply doesn't render the tag if you don't have permission to see inside the group being tagged. |
The change in #88452 added internal/private member support for
JsonIncludeAttribute
/JsonConstructorAttribute
in the reflection-based serializer. We should consider extending this to the source generator via the recent feature introduced in #81741. Note that this would only work for the latest TFMs (with some reflection-based fallback being necessary in older targets).cc @jkotas
The text was updated successfully, but these errors were encountered: