-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Revise System.Text.Json APIs for source generator before it becomes stable
The new System.Text.Json source generator was introduced in https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator. However, I don't think those APIs are in right shapes. They should take use of new language feature introduced in C# 10 and .NET 6, and furthermore, some upcoming features in near future.
Abstract statics in interface
.NET 6 and C# 10 introduced "abstract statics in interface" which allows virtual static to be declared in interfaces and to be implemented in classes/structs. System.Text.Json APIs for source generator should take use of them.
I.e. instead of introducing JsonTypeInfo and APIs consumes JsonTypeInfo in System.Text.Json, a new interface IJsonSerializable containing static abstract method SerializeJson and DeserializeJson should be introduced.
interface IJsonSerializable<T> where T : IJsonSerializable<T>
{
abstract string SerializeJson(T obj, JsonSerializerOptions options);
abstract T DeserializeJson(string str, JsonSerializerOptions options);
}And then users can mark their types with partial and apply JsonSerializable attribute to types they want to generate code for serialization, then the source generator should implement IJsonSerializable<T> for them in a partial class/struct. This approach
not only requires no API changes but also avoid the versioning issue, and the serializer/deserializer can know whether a type implemented IJsonSerializable from T directly without need of any instance. Furthermore, this also allows users to implement their own serialization methods.
Generic attributes
C# 10 is introducing generic attributes, so instead of an attribute receiving a Type as parameter, the attribute itself should be made generic:
public JsonSerializableAttribute<T> : Attribute where T : IJsonSerializable<T> { }So users can use [JsonSerializable<T>] to mark types they want to do serialization. This approach not only gets rid of typeof(T) usage, but also applys constraints to T to satisfy IJsonSerializable<T>.
Extension interfaces
C# will soon introduce extension interfaces in near future, so source generator can make use of this feature to extend the interface IJsonSerializable for existing types and users will no longer need to mark their types as partial.
This will also enable scenario for generating (de)serialization code for not owned types.
Solution
The existing bunch of APIs introduced in System.Text.Json for source generators are totally unnecessary at all, please don't ship those APIs to stable because they will soon be out-of-date. They should be removed.
namespace System.Text.Json
{
public static class JsonSerializer
{
- public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerContext context) => ...;
- public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context) => ...;
- public static object? Deserialize(string json, Type returnType, JsonSerializerContext context) => ...;
- public static object? Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerContext context) => ...;
- public static ValueTask<object?> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static ValueTask<TValue?> DeserializeAsync<TValue>(Stream utf8Json, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static TValue? Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
- public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
- public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
- public static TValue? Deserialize<TValue>(ref Utf8JsonReader reader, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
- public static string Serialize(object? value, Type inputType, JsonSerializerContext context) => ...;
- public static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerContext context) { }
- public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSerializerContext context) => ...;
- public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
- public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) { }
- public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...;
}
}
namespace System.Net.Http.Json
{
public static partial class HttpClientJsonExtensions
{
- public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<object?> GetFromJsonAsync(this HttpClient client, System.Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
}
public static partial class HttpContentJsonExtensions
{
- public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...;
- public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...;
}
public sealed partial class JsonContent : HttpContent
{
- public static JsonContent<object?> Create(object? inputValue, Type inputType, JsonSerializerContext context, MediaTypeHeaderValue? mediaType = null) => ...;
- public static JsonContent<TValue> Create<TValue>(TValue? inputValue, JsonTypeInfo<TValue> jsonTypeInfo, MediaTypeHeaderValue? mediaType = null) => ...;
}
public sealed partial class JsonContent<TValue> : JsonContent
{
- public TValue? Value { get }
}
}And reimplement the source generator with help of abstract statics in interface, then add APIs with constraints IJsonSerialiable<T> to use generated code for serialization, or modify existing APIs to use generated code for those who implemented IJsonSerializable<T>.
namespace System.Text.Json
{
public static class JsonSerializer
{
public string Serialize<T>(T obj, JsonSerializerOptions options) where T : IJsonSerializable<T>
{
return T.Serialize(obj, options);
}
public T Deserialize<T>(string str, JsonSerializerOptions options) where T : IJsonSerializable<T>
{
return T.Deserialize(str, options);
}
// also for Span<> and async APIs ......
}
}Since abstract statics in interface will be provided as a preview feature in .NET 6, the System.Text.Json source generator should also be provided as a preview feature.
And after extension interfaces being shipped, reimplement the source generator to use extension interfaces instead of requiring users to mark their types as partial.