Skip to content

System.Text.Json uses inconsistent polymorphism semantics with custom object converters #72681

@eiriktsarpalis

Description

@eiriktsarpalis

System.Text.Json hardcodes polymorphic serialization when serializing root-level values of type object, regardless of whether the registered JsonConverter<object> supports polymorphism. While this is consistent with the semantics of the built-in converter for object, it can result in inconsistent serialization contracts when used in conjunction with custom converters, depending on whether the value is serialized at the root level or as a nested node:

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

var options = new JsonSerializerOptions { Converters = { new CustomObjectConverter() } };

Console.WriteLine(JsonSerializer.Serialize<object>(0, options)); // Prints 0, custom converter not honored
Console.WriteLine(JsonSerializer.Serialize<object[]>(new object[] { 0 }, options)); // Prints [42], custom converter honored

public class CustomObjectConverter : JsonConverter<object>
{
    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        => writer.WriteNumberValue(42);

    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => throw new NotImplementedException();
}

This issue proposes that we change the root-level behavior for custom object converters so that it no longer defaults to polymorphism (unless a polymorphic converter is being used). This will make serialization contracts consistent, regardless of whether the instance is serialized as a root-level value or as a nested value.

This change might be a breaking change for users that rely on the current behavior. As a workaround, users can get back the existing polymorphic behavior by calling into one of the untyped JsonSerializer APIs, explicitly passing the runtime type of the value as the inputType parameter:

-JsonSerializer.Serialize<object?>(value, options);
+Type runtimeType = value?.GetType() ?? typeof(object);
+JsonSerializer.Serialize(value, runtimeType, options);

Related to #54436 and #72187.

Metadata

Metadata

Labels

area-System.Text.Jsonbreaking-changeIssue or PR that represents a breaking API or functional change over a previous release.enhancementProduct code improvement that does NOT require public API changes/additionsneeds-breaking-change-doc-createdBreaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions