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

System.Text.Json code generator: custom converter ignored? #58119

Closed
Jericho opened this issue Aug 25, 2021 · 6 comments · Fixed by #58291
Closed

System.Text.Json code generator: custom converter ignored? #58119

Jericho opened this issue Aug 25, 2021 · 6 comments · Fixed by #58291

Comments

@Jericho
Copy link

Jericho commented Aug 25, 2021

I have a class that contains the following property (notice the presence of a custom converter):

[JsonPropertyName("tls")]
[JsonConverter(typeof(IntegerBooleanConverter))]
public bool Tls { get; set; }

The following snippet gets generated:

writer.WriteBoolean(tlsPropName, value.Tls);

Correct me if I'm wrong but this seems to ignore my custom converter. I was expecting something along the lines of:

writer.WritePropertyName(tlsPropName);
global::System.Text.Json.JsonSerializer.Serialize(writer, value.Tls, global::MyLibrary.Utilities.MyJsonSerializerContext.Default.Tls);
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Aug 25, 2021
@ghost
Copy link

ghost commented Aug 25, 2021

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

I have a class that contains the following property (notice the presence of a custom converter):

[JsonPropertyName("tls")]
[JsonConverter(typeof(IntegerBooleanConverter))]
public bool Tls { get; set; }

The following snippet gets generated:

writer.WriteBoolean(tlsPropName, value.Tls);

Correct me if I'm wrong but this seems to ignore my custom converter. I was expecting something along the lines of:

writer.WritePropertyName(tlsPropName);
global::System.Text.Json.JsonSerializer.Serialize(writer, value.Tls, global::MyLibrary.Utilities.MyJsonSerializerContext.Default.Tls);
Author: Jericho
Assignees: -
Labels:

area-System.Text.Json, untriaged

Milestone: -

@eiriktsarpalis
Copy link
Member

This is a bug only impacting serialization. Minimal reproduction:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Repro
{
    public static partial class Program
    {
        public static void Main()
        {
            MyClass value = JsonSerializer.Deserialize(@"{""MyProperty"":5}", MyJsonContext.Default.MyClass)!;
            Console.WriteLine(value.MyProperty); // Correctly deserializes as 4
            Console.WriteLine(JsonSerializer.Serialize(value, MyJsonContext.Default.MyClass)); // incorrectly serializes as {"MyProperty":4}
        }

        [JsonSerializable(typeof(MyClass))]
        internal partial class MyJsonContext : JsonSerializerContext
        {
        }

        internal class MyClass
        {
            [JsonConverter(typeof(MyIntegerConverter))]
            public int MyProperty { get; set; }
        }

        public class MyIntegerConverter : JsonConverter<int>
        {
            public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetInt32() - 1;
            public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) => writer.WriteNumberValue(value + 1);
        }
    }
}

We have test coverage for deserialization but we seem to be missing equivalent testing for serialization.

@eiriktsarpalis eiriktsarpalis added this to the 6.0.0 milestone Aug 26, 2021
@eiriktsarpalis eiriktsarpalis added bug blocking-release and removed untriaged New issue has not been triaged by the area owner labels Aug 26, 2021
@eiriktsarpalis
Copy link
Member

Also, note that the issue can be worked around by attaching the JsonConverter attribute to the class definition rather than the property.

@Jericho
Copy link
Author

Jericho commented Aug 26, 2021

Please note that the workaround you suggested does not work for the scenario I presented. I cannot change the bool class definition.

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Aug 27, 2021

You can work around the issue by making the following change to your serialization context:

[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyClass))]
internal partial class MyJsonContext : JsonSerializerContext
{
}

Root cause of the issue is that source gen by default uses fast-path serialization, which by design does not implement all features found in metadata-based serialization. The reason this does not impact deserialization is that there's no fast-path deserialization implemented yet. Note that switching to metadata-based serialization will result in slower serialization performance.

@layomia @ericstj should we consider making metadata-based serialization the default? As seen in this example, the current default configuration can result in non-roundtrippable classes. We should make sure that the default configuration just works even if doesn't result in optimal performance.

@ericstj
Copy link
Member

ericstj commented Aug 27, 2021

We shouldn't need to change defaults. Here's my understanding of how things should work.

  1. If a type can't be supported through fast path deserialization based on information available at compile time we should tell the user and they can make the change. We should not generate serialization logic that ignores part of the serialization contract.
  2. If a type can't be supported through fast path deserialization based on information available at runtime (EG: converter added at runtime), we should fallback to using metadata if present.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 27, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Sep 2, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Oct 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.