Skip to content
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

[API Proposal]: Source Generate System.Text.Json Context Class #80220

Open
squidink7 opened this issue Jan 5, 2023 · 7 comments
Open

[API Proposal]: Source Generate System.Text.Json Context Class #80220

squidink7 opened this issue Jan 5, 2023 · 7 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json
Milestone

Comments

@squidink7
Copy link

Background and motivation

I am currently using C# to create small self-contained applications with NativeAOT, many of which require JSON serialization for either saving application settings or receiving data from remote APIs. As NativeAOT does not (currently) work with reflection, I have been exclusively using source-generated serializers which require a bit more code to get working, in the form of the serialization context (which can become increasingly hard to maintain as an application grows, see #73297). While I'm not too familiar with the way the source-generator works behind the scenes or if this would even be possible with the current situation, simply comparing the flow of serializing an object to json to other AOT languages such as Dart, Kotlin/Native, and Swift there seems to be a lot less friction in those languages as compared to .NET. I was wondering if there could be a way for the source generator to write this class instead of the user.

API Proposal

Ideally the source generator would create the context class when generating the rest of the required code, and store it in a standard location for the Serialize and Deserialize methods to search unless an existing context class is provided.

(Unfortunately I am not experienced enough to determine how this would take form in framework code)

API Usage

If it's possible, having a default internal context for JsonSerializer to search would simplify the calls to Serialize/Deserialize, as the programmer no longer has to write

(example code modified from learn.microsoft.com)

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast));

[JsonSerializable]
class WeatherForecast {
    [...]
}

Alternative Designs

Currently reflection-based APIs use the same method signature as the proposal above, so to explicitly specify source generation as the method to use could help resolve this conflict.

sourceGenOptions = new JsonSerializerOptions
{
    SerializerMode = JsonSerializerMode.SourceGen
};

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast), sourceGenOptions);

or

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast), SourceGen = true);

Risks

This reduces flexibility, as many options that would traditionally be passed through the context, and is therefore not intended to replace the existing method, but provide a syntactically simpler alternative for small applications.
These changes would be breaking, and require refactoring existing Serialization code. Provided the current method of writing a custom context class remains, it shouldn't cause too much breakage for current code-bases.

@squidink7 squidink7 added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jan 5, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 5, 2023
@ghost
Copy link

ghost commented Jan 5, 2023

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

I am currently using C# to create small self-contained applications with NativeAOT, many of which require JSON serialization for either saving application settings or receiving data from remote APIs. As NativeAOT does not (currently) work with reflection, I have been exclusively using source-generated serializers which require a bit more code to get working, in the form of the serialization context (which can become increasingly hard to maintain as an application grows, see #73297). While I'm not too familiar with the way the source-generator works behind the scenes or if this would even be possible with the current situation, simply comparing the flow of serializing an object to json to other AOT languages such as Dart, Kotlin/Native, and Swift there seems to be a lot less friction in those languages as compared to .NET. I was wondering if there could be a way for the source generator to write this class instead of the user.

API Proposal

Ideally the source generator would create the context class when generating the rest of the required code, and store it in a standard location for the Serialize and Deserialize methods to search unless an existing context class is provided.

(Unfortunately I am not experienced enough to determine how this would take form in framework code)

API Usage

If it's possible, having a default internal context for JsonSerializer to search would simplify the calls to Serialize/Deserialize, as the programmer no longer has to write

(example code modified from learn.microsoft.com)

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast));

[JsonSerializable]
class WeatherForecast {
    [...]
}

Alternative Designs

Currently reflection-based APIs use the same method signature as the proposal above, so to explicitly specify source generation as the method to use could help resolve this conflict.

sourceGenOptions = new JsonSerializerOptions
{
    SerializerMode = JsonSerializerMode.SourceGen
};

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast), sourceGenOptions);

or

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast), SourceGen = true);

Risks

This reduces flexibility, as many options that would traditionally be passed through the context, and is therefore not intended to replace the existing method, but provide a syntactically simpler alternative for small applications.
These changes would be breaking, and require refactoring existing Serialization code. Provided the current method of writing a custom context class remains, it shouldn't cause too much breakage for current code-bases.

Author: squidink7
Assignees: -
Labels:

api-suggestion, area-System.Text.Json

Milestone: -

@Clockwork-Muse
Copy link
Contributor

I was wondering if there could be a way for the source generator to write this class instead of the user.

Strictly speaking it's likely possible. The problem is that if you put such a context in a "well known" location you - and every library you might reference - can have only one copy, which would be a fun way to cause waves of breaking changes throughout the ecosystem. You could instead segment it by having a context object for each namespace an object is from, but namespaces can have objects from multiple packages, so it's still possible to cause breaking waves, plus all the fun of having multiple contexts that you're trying to juggle. So the most realistic version is the instance listed in that other issue.

These changes would be breaking

This means it's extremely unlikely to happen.

Alternative Designs

Source generators, as a feature, run solely at compile time, so you couldn't make it a runtime option. They're also strictly additive, so you can't replace those calls at compile time with something else either.

@Tornhoof
Copy link
Contributor

Tornhoof commented Jan 5, 2023

Source Generators do not run ordered at the moment, it is afaik not possible to have one source generator depend on the output of another one.

@layomia
Copy link
Contributor

layomia commented Jan 16, 2023

Yeah we could expand on the current gesture for the source generator by allowing the [JsonSerializable] a directly on types. We'd need to establish a pattern for retrieving this metadata. I did initially envision the implicit creation of a "default" context for which types with the attribute are generated to, but that poses problems like @Clockwork-Muse points out. The design needs further thought but, if needed we could create an API to indicate the name/namespace of this implicit context. I imagine this would mitigate many of the concerns.

This pattern was discussed in the early period of the JSON source generator design and punted since it wasn't necessary for v1. With strong enough indication that this is a widely-felt problem we could come up with a solution here.

@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Jan 16, 2023
@layomia layomia added this to the Future milestone Jan 16, 2023
@krwq
Copy link
Member

krwq commented Jan 16, 2023

On top of what @Clockwork-Muse said - I'm not sure how would we handle different options for those types. We definitely can't generate code for every possible option

@squidink7
Copy link
Author

Perhaps we could pass an instance of JsonSerializerOptions as an optional argument to the [JsonSerializable] decorators.
E.g.

class JsonConfig {
    public static JsonSerializerOptions myJsonOptions = new();
    ...
}

[JsonSerializable(Options = JsonConfig.myJsonOptions)]
class JsonResult {
...

@Clockwork-Muse
Copy link
Contributor

Attribute arguments must be constants, so that version doesn't work. Probably, you'd want to have a factory interface you were required to implement that would return the option, which you could specify via type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json
Projects
None yet
Development

No branches or pull requests

5 participants