diff --git a/src/Dahomey.Json.Tests/DiscriminatorTests.cs b/src/Dahomey.Json.Tests/DiscriminatorTests.cs index 46ce964..e98c6fb 100644 --- a/src/Dahomey.Json.Tests/DiscriminatorTests.cs +++ b/src/Dahomey.Json.Tests/DiscriminatorTests.cs @@ -35,6 +35,18 @@ public class OtherObject { } + public class BaseObjectResponse + { + public int Id { get; set; } + } + + [JsonDiscriminator(12)] + public class NameObjectResponse : BaseObjectResponse + { + public string Name { get; set; } + } + + public class DiscriminatorTests { [Fact] @@ -107,10 +119,45 @@ public void WritePolymorphicObject(DiscriminatorPolicy discriminatorPolicy, stri Assert.Equal(expected, actual); } + [Theory] + [InlineData(DiscriminatorPolicy.Default, @"{""BaseObject"":{""$type"":12,""Name"":""foo"",""Id"":1},""NameObject"":{""Name"":""bar"",""Id"":2}}")] + [InlineData(DiscriminatorPolicy.Auto, @"{""BaseObject"":{""$type"":12,""Name"":""foo"",""Id"":1},""NameObject"":{""Name"":""bar"",""Id"":2}}")] + [InlineData(DiscriminatorPolicy.Never, @"{""BaseObject"":{""Name"":""foo"",""Id"":1},""NameObject"":{""Name"":""bar"",""Id"":2}}")] + [InlineData(DiscriminatorPolicy.Always, @"{""BaseObject"":{""$type"":12,""Name"":""foo"",""Id"":1},""NameObject"":{""$type"":12,""Name"":""bar"",""Id"":2}}")] + public void WritePolymorphicObjectDuplicateKey(DiscriminatorPolicy discriminatorPolicy, string expected) + { + JsonSerializerOptions options = new JsonSerializerOptions(); + options.SetupExtensions(); + DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry(); + registry.ClearConventions(); + registry.RegisterConvention(new DefaultDiscriminatorConvention(options)); + registry.RegisterType(); + registry.RegisterType(); + registry.DiscriminatorPolicy = discriminatorPolicy; + + BaseObjectHolder obj = new BaseObjectHolder + { + BaseObject = new NameObject + { + Id = 1, + Name = "foo" + }, + NameObject = new NameObject + { + Id = 2, + Name = "bar" + } + }; + + string actual = JsonSerializer.Serialize(obj, options); + + Assert.Equal(expected, actual); + } + private class CustomDiscriminatorConvention : IDiscriminatorConvention { private readonly ReadOnlyMemory _memberName = Encoding.ASCII.GetBytes("type"); - private readonly Dictionary _typesByDiscriminator = new Dictionary(); + private readonly Dictionary> _typesByDiscriminator = new (); private readonly Dictionary _discriminatorsByType = new Dictionary(); public ReadOnlySpan MemberName => _memberName.Span; @@ -123,20 +170,22 @@ public bool TryRegisterType(Type type) discriminator = discriminator * 23 + (int)c; } - _typesByDiscriminator.Add(discriminator, type); + if(!_typesByDiscriminator.ContainsKey(discriminator)) + _typesByDiscriminator.Add(discriminator, new List()); + _typesByDiscriminator[discriminator].Add(type); _discriminatorsByType.Add(type, discriminator); return true; } - public Type ReadDiscriminator(ref Utf8JsonReader reader) + public IEnumerable ReadDiscriminator(ref Utf8JsonReader reader) { int discriminator = reader.GetInt32(); - if (!_typesByDiscriminator.TryGetValue(discriminator, out Type type)) + if (!_typesByDiscriminator.TryGetValue(discriminator, out List types)) { throw new JsonException($"Unknown type discriminator: {discriminator}"); } - return type; + return types; } public void WriteDiscriminator(Utf8JsonWriter writer, Type actualType) diff --git a/src/Dahomey.Json/Serialization/Conventions/DefaultDiscriminatorConvention.cs b/src/Dahomey.Json/Serialization/Conventions/DefaultDiscriminatorConvention.cs index d2065c4..1b26a95 100644 --- a/src/Dahomey.Json/Serialization/Conventions/DefaultDiscriminatorConvention.cs +++ b/src/Dahomey.Json/Serialization/Conventions/DefaultDiscriminatorConvention.cs @@ -12,7 +12,7 @@ public class DefaultDiscriminatorConvention : IDiscriminatorConvention { private readonly JsonSerializerOptions _options; private readonly ReadOnlyMemory _memberName; - private readonly Dictionary _typesByDiscriminator = new(); + private readonly Dictionary> _typesByDiscriminator = new(); private readonly Dictionary _discriminatorsByType = new(); private readonly JsonConverter _jsonConverter; @@ -40,11 +40,15 @@ public bool TryRegisterType(Type type) } _discriminatorsByType[type] = discriminator; - _typesByDiscriminator.Add(discriminator, type); + if(!_typesByDiscriminator.ContainsKey(discriminator)) + { + _typesByDiscriminator.Add(discriminator, new List()); + } + _typesByDiscriminator[discriminator].Add(type); return true; } - public Type ReadDiscriminator(ref Utf8JsonReader reader) + public IEnumerable ReadDiscriminator(ref Utf8JsonReader reader) { T? discriminator = _jsonConverter.Read(ref reader, typeof(T), _options); @@ -53,11 +57,11 @@ public Type ReadDiscriminator(ref Utf8JsonReader reader) throw new JsonException($"Null discriminator"); } - if (!_typesByDiscriminator.TryGetValue(discriminator, out Type? type)) + if (!_typesByDiscriminator.TryGetValue(discriminator, out List? types)) { throw new JsonException($"Unknown type discriminator: {discriminator}"); } - return type; + return types; } public void WriteDiscriminator(Utf8JsonWriter writer, Type actualType) diff --git a/src/Dahomey.Json/Serialization/Conventions/IDiscriminatorConvention.cs b/src/Dahomey.Json/Serialization/Conventions/IDiscriminatorConvention.cs index 265c562..9200ab8 100644 --- a/src/Dahomey.Json/Serialization/Conventions/IDiscriminatorConvention.cs +++ b/src/Dahomey.Json/Serialization/Conventions/IDiscriminatorConvention.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.Json; namespace Dahomey.Json.Serialization.Conventions @@ -7,7 +8,7 @@ public interface IDiscriminatorConvention { ReadOnlySpan MemberName { get; } bool TryRegisterType(Type type); - Type ReadDiscriminator(ref Utf8JsonReader reader); + IEnumerable ReadDiscriminator(ref Utf8JsonReader reader); void WriteDiscriminator(Utf8JsonWriter writer, Type actualType); } } diff --git a/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs b/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs index 1938c75..07255bc 100644 --- a/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs +++ b/src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs @@ -4,6 +4,7 @@ using Dahomey.Json.Util; using System.Text.Json; using System; +using System.Linq; using System.Text; using Dahomey.Json.Serialization.Converters.Mappings; using Dahomey.Json.Attributes; @@ -309,11 +310,12 @@ private void ReadMember(ref Utf8JsonReader reader, ref T obj, ref IObjectConvert if (FindItem(ref findReader, _discriminatorConvention.MemberName)) { // discriminator value - Type actualType = _discriminatorConvention.ReadDiscriminator(ref findReader); + var discriminatorTypes = _discriminatorConvention.ReadDiscriminator(ref findReader); + Type? actualType = discriminatorTypes.FirstOrDefault(d => _objectMapping.ObjectType.IsAssignableFrom(d)); - if (!_objectMapping.ObjectType.IsAssignableFrom(actualType)) + if (actualType == null) { - throw new JsonException($"expected type {_objectMapping.ObjectType} is not assignable from actual type {actualType}"); + throw new JsonException($"no assignable type found for expected type {_objectMapping.ObjectType}"); } converter = (IObjectConverter)options.GetConverter(actualType);