Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#113 - Duplicate Key if several type have the same discriminator #114

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions src/Dahomey.Json.Tests/DiscriminatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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<int>(options));
registry.RegisterType<NameObject>();
registry.RegisterType<NameObjectResponse>();
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<byte> _memberName = Encoding.ASCII.GetBytes("type");
private readonly Dictionary<int, Type> _typesByDiscriminator = new Dictionary<int, Type>();
private readonly Dictionary<int, List<Type>> _typesByDiscriminator = new ();
private readonly Dictionary<Type, int> _discriminatorsByType = new Dictionary<Type, int>();

public ReadOnlySpan<byte> MemberName => _memberName.Span;
Expand All @@ -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<Type>());
_typesByDiscriminator[discriminator].Add(type);
_discriminatorsByType.Add(type, discriminator);

return true;
}

public Type ReadDiscriminator(ref Utf8JsonReader reader)
public IEnumerable<Type> ReadDiscriminator(ref Utf8JsonReader reader)
{
int discriminator = reader.GetInt32();
if (!_typesByDiscriminator.TryGetValue(discriminator, out Type type))
if (!_typesByDiscriminator.TryGetValue(discriminator, out List<Type> types))
{
throw new JsonException($"Unknown type discriminator: {discriminator}");
}
return type;
return types;
}

public void WriteDiscriminator(Utf8JsonWriter writer, Type actualType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class DefaultDiscriminatorConvention<T> : IDiscriminatorConvention
{
private readonly JsonSerializerOptions _options;
private readonly ReadOnlyMemory<byte> _memberName;
private readonly Dictionary<T, Type> _typesByDiscriminator = new();
private readonly Dictionary<T, List<Type>> _typesByDiscriminator = new();
private readonly Dictionary<Type, T> _discriminatorsByType = new();
private readonly JsonConverter<T> _jsonConverter;

Expand Down Expand Up @@ -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<Type>());
}
_typesByDiscriminator[discriminator].Add(type);
return true;
}

public Type ReadDiscriminator(ref Utf8JsonReader reader)
public IEnumerable<Type> ReadDiscriminator(ref Utf8JsonReader reader)
{
T? discriminator = _jsonConverter.Read(ref reader, typeof(T), _options);

Expand All @@ -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<Type>? types))
{
throw new JsonException($"Unknown type discriminator: {discriminator}");
}
return type;
return types;
}

public void WriteDiscriminator(Utf8JsonWriter writer, Type actualType)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text.Json;

namespace Dahomey.Json.Serialization.Conventions
Expand All @@ -7,7 +8,7 @@ public interface IDiscriminatorConvention
{
ReadOnlySpan<byte> MemberName { get; }
bool TryRegisterType(Type type);
Type ReadDiscriminator(ref Utf8JsonReader reader);
IEnumerable<Type> ReadDiscriminator(ref Utf8JsonReader reader);
void WriteDiscriminator(Utf8JsonWriter writer, Type actualType);
}
}
8 changes: 5 additions & 3 deletions src/Dahomey.Json/Serialization/Converters/ObjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down