Skip to content

System.Text.Json fast path serialization not working with combined contexts #71933

@eiriktsarpalis

Description

@eiriktsarpalis

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:

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

if (SerializeHandler != null && Options.SerializerContext?.CanUseSerializationLogic == true)
{
ThrowOnDeserialize = true;
return;
}

or

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;
}

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions