-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
EDIT: See API proposal at the bottom of this post.
The internal logic governing use of fast path serialization is dependent on JsonSerializerOptions encapsulating a JsonSerializerContext instance. Consider:
Lines 72 to 80 in 7d2be1e
| if (!state.SupportContinuation && | |
| jsonTypeInfo is JsonTypeInfo<T> info && | |
| info.SerializeHandler != null && | |
| !state.CurrentContainsMetadata && // Do not use the fast path if state needs to write metadata. | |
| info.Options.SerializerContext?.CanUseSerializationLogic == true) | |
| { | |
| info.SerializeHandler(writer, value); | |
| return true; | |
| } |
or
Lines 130 to 134 in 7d2be1e
| if (SerializeHandler != null && Options.SerializerContext?.CanUseSerializationLogic == true) | |
| { | |
| ThrowOnDeserialize = true; | |
| return; | |
| } |
or
runtime/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
Lines 452 to 455 in 26da83d
| if (converter.ConstructorIsParameterized) | |
| { | |
| InitializeConstructorParameters(GetParameterInfoValues(), sourceGenMode: Options.SerializerContext != null); | |
| } |
The introduction of contract customization has made it possible for source generated metadata to be associated with options instances not attached to a JsonSerializerContext. This can result in inconsistency, consider:
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTemps))]
public partial class SerializationContext : JsonSerializerContext {}
public class HighLowTemps
{
public int High { get; set; }
public int Low { get; set; }
}
[Fact]
public static void CombiningContexts_FastPathSerialization()
{
var value = new HighLowTemps { High = 40, Low = 30 };
string expectedJson = """{"High":40,"Low":30}""";
// Sanity check -- default context
var options = new JsonSerializerOptions { TypeInfoResolver = SerializationContext.Default };
string json = JsonSerializer.Serialize(value, options);
Assert.Equal(expectedJson, json);
// Using a trivial combined context
options = new JsonSerializerOptions { TypeInfoResolver = JsonTypeInfoResolver.Combine(SerializationContext.Default) };
json = JsonSerializer.Serialize(value, options); // 'JsonSerializerContext' '<null>' did not provide property metadata
// for type 'System.Text.Json.SourceGeneration.Tests.HighLowTemps'.
Assert.Equal(expectedJson, json);
}In order to get the issue fixed, it is necessary for source gen JsonTypeInfo<T> to encapsulate a reference to their originating JsonSerializerContext.
API Proposal
namespace System.Text.Json.Serialization.Metadata;
public class JsonTypeInfo
{
public IJsonTypeInfoResolver? OriginatingResolver { get; set; } = null;
}