Skip to content

Polymorphic serialization with STJ #116625

Closed
@danfma

Description

@danfma

Description

Hello, friends!

I'm having an issue with JSON deserialization (serialization is working).

  • If I try to deserialize an item at a time, it works (so that deserialization for the PaymentType works);
  • If I try to deserialize the array (no matter if targeting a HashSet, List, Array, it will fail with that error above);
  • I've tried to use a custom JsonConverter to deserialize the whole collection, but that didn't work too.

The code:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(StableCoins), nameof(StableCoins))]
[JsonDerivedType(typeof(CryptoCurrency), nameof(CryptoCurrency))]
public abstract record PaymentType
{
    public sealed record StableCoins : PaymentType
    {
        public static readonly StableCoins Instance = new();
    }

    public sealed record CryptoCurrency(CryptoCurrencyId Id) : PaymentType;
}

// related classes

public readonly record struct CryptoCurrencyId(Guid Value) : IEntityId<CryptoCurrencyId>
{
    public static CryptoCurrencyId From(Guid value) => new(value);

    public static implicit operator Guid(CryptoCurrencyId id) => id.Value;

    public static implicit operator CryptoCurrencyId(Guid value) => new(value);
}

public sealed class EntityIdJsonConverter<T> : JsonConverter<T>
    where T : struct, IEntityId<T>
{
    public override T Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options
    )
    {
        var value = reader.GetString();
        var guid = string.IsNullOrEmpty(value) ? Guid.Empty : Guid.Parse(value);

        return T.From(guid);
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.Value);
    }
}

[JsonSourceGenerationOptions(
    JsonSerializerDefaults.Web,
    UseStringEnumConverter = true,
    RespectNullableAnnotations = true,
    PreferredObjectCreationHandling = JsonObjectCreationHandling.Replace,
    Converters = [typeof(EntityIdJsonConverter<CryptoCurrencyId>)]
)]
[JsonSerializable(typeof(PaymentType))]
[JsonSerializable(typeof(HashSet<PaymentType>))]
public partial class DbJsonSerializerContext : JsonSerializerContext;

Reproduction Steps

Just try to deserialize the string with or without a JsonSerializerContext.

The test code (XUnit + Shoudly):

public class DatabaseSerializationTests
{
    [Fact]
    public void DeserializeHashSetOfPaymentTypes()
    {
        var json = """
            [{"type": "StableCoins"}, {"id": "01975718-8994-7a90-a305-629d9954912d", "type": "CryptoCurrency"}, {"id": "01975718-8994-785d-8d10-509c194c264f", "type": "CryptoCurrency"}, {"id": "01975718-8994-78eb-bee4-3aae93166b33", "type": "CryptoCurrency"}]
            """;

        var paymentTypes = JsonSerializer.Deserialize(json, DbJsonSerializerContext.Default.HashSetPaymentType);

        paymentTypes.ShouldNotBeNull();
    }
}

Expected behavior

I expect to receive a hash set of instances of payment types.

Actual behavior

I'm receiving the following error.

System.NotSupportedException
The JSON payload for polymorphic interface or abstract type 'Sentrafi.Payments.PaymentType' must specify a type discriminator. Path: $[1] | LineNumber: 0 | BytePositionInLine: 32.
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, Exception innerException)
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(JsonTypeInfo typeInfo, Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, T& value, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Deserialize(Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
   at Sentrafi.Tests.Server.Infrastructure.Persistence.DatabaseSerializationTests.DeserializeHashSetOfPaymentTypes() in /Users/danfma/Develop/Sentrafi/sentrafi-server/Sentrafi.Tests/Server/Infrastructure/Persistence/DatabaseSerializationTests.cs:line 17

Regression?

Apparently, not. I've tried with .NET 8 and .NET 10, and the behavior remains the same.

Known Workarounds

None so far.

Configuration

  • .NET Version: .NET 10 preview 5 (but the project is built with .NET 9)
  • OS: MacOS Sequoia 15.5
  • Architecture: ARM64
  • I've used polymorphism in the same project with other objects and it works;
  • No Blazor, deserialization on the server.

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions