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]: Allow inheritance for Utf8JsonWriter class #111899

Closed
Vijay-Nirmal opened this issue Jan 28, 2025 · 7 comments
Closed

[API Proposal]: Allow inheritance for Utf8JsonWriter class #111899

Vijay-Nirmal opened this issue Jan 28, 2025 · 7 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json

Comments

@Vijay-Nirmal
Copy link

Vijay-Nirmal commented Jan 28, 2025

Background and motivation

Currently, Utf8JsonWriter is masked as sealed to prevent inheritance, which makes sense as sealed classes have slightly better performance but it prevents the users from customizing the output JSON format other than the options provided by the JsonWriterOptions which is very limit and restrictive. For eg: it only allows specific char/string to be used for IndentCharacter and NewLine and doesn't allow customisation of many other things.

A real-world use case for this request is for microsoft/garnet project, where we have to respond to the user in the format they specified in the input for the JSON module which is not supported in Utf8JsonWriter as of today. For this use case, there are only two options, either Garnet can never support the customizability provided by Redis for certain Json commands, or Garnet should use Newtonsoft.Json (which supports customisation). Both of these options are not ideal.

API Proposal

public partial class Utf8JsonWriter : IDisposable, IAsyncDisposable // Removed sealed
{
    // Adding virtual to all methods related to write option
    public virtual void WriteBase64String(string propertyName, ReadOnlySpan<byte> bytes);
    public virtual void WriteBase64String(JsonEncodedText propertyName, ReadOnlySpan<byte> bytes);
    public virtual void WriteBase64String(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes);
    public virtual void WriteBase64String(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> bytes);
    public virtual void WriteBase64StringValue(ReadOnlySpan<byte> bytes);
    public virtual void WriteBoolean(string propertyName, bool value);
    public virtual void WriteBoolean(JsonEncodedText propertyName, bool value);
    public virtual void WriteBoolean(ReadOnlySpan<char> propertyName, bool value);
    public virtual void WriteBoolean(ReadOnlySpan<byte> utf8PropertyName, bool value);
    public virtual void WriteBooleanValue(bool value);
    public virtual void WriteCommentValue(ReadOnlySpan<char> value);
    public virtual void WriteCommentValue(string value);
    public virtual void WriteCommentValue(ReadOnlySpan<byte> utf8Value);
    public virtual void WriteEndArray();
    public virtual void WriteEndObject();
    public virtual void WriteNull(ReadOnlySpan<char> propertyName);
    public virtual void WriteNull(JsonEncodedText propertyName);
    public virtual void WriteNull(string propertyName);
    public virtual void WriteNull(ReadOnlySpan<byte> utf8PropertyName);
    public virtual void WriteNullValue();
    public virtual void WriteNumber(string propertyName, long value);
    public virtual void WriteNumber(JsonEncodedText propertyName, ulong value);
    public virtual void WriteNumber(JsonEncodedText propertyName, uint value);
    public virtual void WriteNumber(JsonEncodedText propertyName, float value);
    public virtual void WriteNumber(JsonEncodedText propertyName, long value);
    public virtual void WriteNumber(JsonEncodedText propertyName, int value);
    public virtual void WriteNumber(JsonEncodedText propertyName, double value);
    public virtual void WriteNumber(JsonEncodedText propertyName, decimal value);
    public virtual void WriteNumber(string propertyName, ulong value);
    public virtual void WriteNumber(string propertyName, uint value);
    public virtual void WriteNumber(string propertyName, float value);
    public virtual void WriteNumber(string propertyName, int value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, float value);
    public virtual void WriteNumber(string propertyName, decimal value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, ulong value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, uint value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, float value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, long value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, int value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, double value);
    public virtual void WriteNumber(ReadOnlySpan<char> propertyName, decimal value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, ulong value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, uint value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, long value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, int value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, double value);
    public virtual void WriteNumber(ReadOnlySpan<byte> utf8PropertyName, decimal value);
    public virtual void WriteNumber(string propertyName, double value);
    public virtual void WriteNumberValue(uint value);
    public virtual void WriteNumberValue(ulong value);
    public virtual void WriteNumberValue(int value);
    public virtual void WriteNumberValue(decimal value);
    public virtual void WriteNumberValue(double value);
    public virtual void WriteNumberValue(float value);
    public virtual void WriteNumberValue(long value);
    public virtual void WritePropertyName(ReadOnlySpan<byte> utf8PropertyName);
    public virtual void WritePropertyName(JsonEncodedText propertyName);
    public virtual void WritePropertyName(string propertyName);
    public virtual void WritePropertyName(ReadOnlySpan<char> propertyName);
    public virtual void WriteRawValue(ReadOnlySequence<byte> utf8Json, bool skipInputValidation = false);
    public virtual void WriteRawValue(ReadOnlySpan<byte> utf8Json, bool skipInputValidation = false);
    public virtual void WriteRawValue([StringSyntax("Json")] string json, bool skipInputValidation = false);
    public virtual void WriteRawValue([StringSyntax("Json")] ReadOnlySpan<char> json, bool skipInputValidation = false);
    public virtual void WriteStartArray(ReadOnlySpan<byte> utf8PropertyName);
    public virtual void WriteStartArray(ReadOnlySpan<char> propertyName);
    public virtual void WriteStartArray(string propertyName);
    public virtual void WriteStartArray(JsonEncodedText propertyName);
    public virtual void WriteStartArray();
    public virtual void WriteStartObject(JsonEncodedText propertyName);
    public virtual void WriteStartObject(ReadOnlySpan<char> propertyName);
    public virtual void WriteStartObject(ReadOnlySpan<byte> utf8PropertyName);
    public virtual void WriteStartObject();
    public virtual void WriteStartObject(string propertyName);
    public virtual void WriteString(string propertyName, ReadOnlySpan<char> value);
    public virtual void WriteString(string propertyName, string? value);
    public virtual void WriteString(string propertyName, JsonEncodedText value);
    public virtual void WriteString(JsonEncodedText propertyName, DateTime value);
    public virtual void WriteString(JsonEncodedText propertyName, string? value);
    public virtual void WriteString(JsonEncodedText propertyName, Guid value);
    public virtual void WriteString(JsonEncodedText propertyName, ReadOnlySpan<byte> utf8Value);
    public virtual void WriteString(JsonEncodedText propertyName, ReadOnlySpan<char> value);
    public virtual void WriteString(JsonEncodedText propertyName, JsonEncodedText value);
    public virtual void WriteString(string propertyName, ReadOnlySpan<byte> utf8Value);
    public virtual void WriteString(JsonEncodedText propertyName, DateTimeOffset value);
    public virtual void WriteString(string propertyName, Guid value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, string? value);
    public virtual void WriteString(string propertyName, DateTime value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, DateTime value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value);
    public virtual void WriteString(string propertyName, DateTimeOffset value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> utf8Value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<char> value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, string? value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, Guid value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, DateTime value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, DateTimeOffset value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, ReadOnlySpan<byte> utf8Value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, ReadOnlySpan<char> value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, JsonEncodedText value);
    public virtual void WriteString(ReadOnlySpan<byte> utf8PropertyName, JsonEncodedText value);
    public virtual void WriteString(ReadOnlySpan<char> propertyName, Guid value);
    public virtual void WriteStringValue(DateTime value);
    public virtual void WriteStringValue(DateTimeOffset value);
    public virtual void WriteStringValue(Guid value);
    public virtual void WriteStringValue(ReadOnlySpan<byte> utf8Value);
    public virtual void WriteStringValue(ReadOnlySpan<char> value);
    public virtual void WriteStringValue(string? value);
    public virtual void WriteStringValue(JsonEncodedText value);
}

API Usage

public sealed class CustomUtf8JsonWriter : Utf8JsonWriter
{
    // override any method
}


var writer = new CustomUtf8JsonWriter(steam);
JsonSerializer.Serialize(writer, jsonNode, options);

Alternative Designs

No response

Risks

We will lose the "slightly better performance" of sealed modifier

@Vijay-Nirmal Vijay-Nirmal added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jan 28, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Jan 28, 2025
Copy link
Contributor

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

@huoyaoyuan
Copy link
Member

Changing non-virtual method to virtual is massive breaking change and can't happen. It also affects performance significantly.

@eiriktsarpalis
Copy link
Member

There's also lack of symmetry vis-a-vis Utf8JsonReader which is a ref struct and therefore can never support inheritance.

@Vijay-Nirmal
Copy link
Author

Vijay-Nirmal commented Jan 28, 2025

@eiriktsarpalis @eiriktsarpalis Can we do something else to support customization of output JSON format? Without much overhead from a maintenance and performance perspective. I didn't know that changing non-virtual methods to virtual causes a breaking change even if the base class was sealed before.

@eiriktsarpalis
Copy link
Member

Depends on what you mean by customization. If you want to be able to customize the JSON produced for particular types then JsonConverter is the way to go. If you're trying to output something other than JSON, then unfortunately this isn't supported.

@Vijay-Nirmal
Copy link
Author

Vijay-Nirmal commented Jan 28, 2025

Basically I want to control the format of the output Json. Below is a simple example

Input Json: {"a":1}
Current Output: {"a":1}
Customized Output: { "a": 1 }
Crazy Customized Output: {$$"a":$$1$$} (Using $$ for space)

@eiriktsarpalis
Copy link
Member

It's unlikely we'd ever be able to support his given how STJ is currently designed. You might consider using the JsonWriter class from Json.NET which is abstractable.

@eiriktsarpalis eiriktsarpalis closed this as not planned Won't fix, can't repro, duplicate, stale Jan 28, 2025
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Jan 28, 2025
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

3 participants