From a7ac836576e2d03ff0887c3873f6fc72349a1e4a Mon Sep 17 00:00:00 2001 From: manuc66 Date: Sat, 15 Nov 2025 23:29:32 +0100 Subject: [PATCH 1/3] SHould fix #177 --- JsonSubTypes.Tests/GenericTests.cs | 47 ++++++++++++++++++- JsonSubTypes/JsonSubtypes.cs | 15 ++++++ ...onSubtypesByDiscriminatorValueConverter.cs | 11 ++++- JsonSubTypes/JsonSubtypesConverter.cs | 43 ++++++++++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) diff --git a/JsonSubTypes.Tests/GenericTests.cs b/JsonSubTypes.Tests/GenericTests.cs index 9eaf8f4..cba4904 100644 --- a/JsonSubTypes.Tests/GenericTests.cs +++ b/JsonSubTypes.Tests/GenericTests.cs @@ -3,7 +3,8 @@ using NUnit.Framework; namespace JsonSubTypes.Tests -{[JsonConverter(typeof(JsonSubtypes), "Type")] +{ + [JsonConverter(typeof(JsonSubtypes), "Type")] [JsonSubtypes.KnownSubType(typeof(Some<>), "Some")] public interface IResult { @@ -49,4 +50,48 @@ public void DeserializingSubTypeWithDateParsesCorrectly() Console.WriteLine(result); } } + + + + [TestFixture] + public class GenericBaseTests + { + abstract class Base + { + public T Value { get; set; } + + public abstract string Kind { get; } + } + + class Nested1 : Base + { + public override string Kind => "1"; + } + + class Nested2: Base + { + public override string Kind => "2"; + } + + [Test] + public void DeserializingSubTypeWithDateParsesCorrectly() + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(JsonSubtypesConverterBuilder + .Of(typeof(Base<>), "Kind") // type property is only defined here + .RegisterSubtype(typeof(Nested1<>), "1") + .RegisterSubtype(typeof(Nested2<>), "2") + //.SerializeDiscriminatorProperty() // ask to serialize the type property + .Build()); + + var json = JsonConvert.SerializeObject(new Nested1 + { + Value = 42, + }, settings); // {"Kind":"1","Value":42} + + var @base = JsonConvert.DeserializeObject>(json, settings); // JsonSerializationException. Could not create an instance of type Base`1[System.Int32]. Type is an interface or abstract class and cannot be instantiated. Path 'Kind', line 1, position 8. + + Assert.AreEqual(42, @base.Value); + } + } } diff --git a/JsonSubTypes/JsonSubtypes.cs b/JsonSubTypes/JsonSubtypes.cs index 41b04b1..f5e7251 100644 --- a/JsonSubTypes/JsonSubtypes.cs +++ b/JsonSubTypes/JsonSubtypes.cs @@ -274,6 +274,12 @@ private Type GetType(JObject jObject, Type parentType, JsonSerializer serializer currentTypeResolver = GetTypeResolver(ToTypeInfo(targetType), jsonConverterCollection); } + if (targetType != null && ToTypeInfo(targetType).IsGenericTypeDefinition && !ToTypeInfo(parentType).IsGenericTypeDefinition) + { + Type closedTargetType = ToTypeInfo(targetType).MakeGenericType(GetGenericTypeArguments(parentType).ToArray()); + return closedTargetType; + } + return targetType; } @@ -532,6 +538,15 @@ internal static TypeInfo ToTypeInfo(Type type) #endif } + internal static IEnumerable GetImplementedInterfaces(Type type) + { +#if (!NETSTANDARD1_3) + return type.GetInterfaces(); +#else + return type?.GetTypeInfo().ImplementedInterfaces; +#endif + } + internal static Type ToType(TypeInfo typeInfo) { #if (!NETSTANDARD1_3) diff --git a/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs b/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs index 42af58f..9607053 100644 --- a/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs +++ b/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -109,7 +110,15 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s if (!_supportedTypes.TryGetValue(value.GetType(), out object supportedType)) { - throw new JsonSerializationException("Impossible to serialize type: " + value.GetType().FullName + " because there is no registered mapping for the discriminator property"); + var matchingGenericSupportedType = _supportedTypes.Keys + .FirstOrDefault(x => InheritsOrImplementsGeneric(value.GetType(), x)); + + if (matchingGenericSupportedType == null || + !_supportedTypes.TryGetValue(matchingGenericSupportedType, out supportedType)) + { + throw new JsonSerializationException("Impossible to serialize type: " + value.GetType().FullName + + " because there is no registered mapping for the discriminator property"); + } } JToken typeMappingPropertyValue = JToken.FromObject(supportedType, serializer); if (_addDiscriminatorFirst) diff --git a/JsonSubTypes/JsonSubtypesConverter.cs b/JsonSubTypes/JsonSubtypesConverter.cs index 8765c98..c38945a 100644 --- a/JsonSubTypes/JsonSubtypesConverter.cs +++ b/JsonSubTypes/JsonSubtypesConverter.cs @@ -1,5 +1,5 @@ using System; -using System.Reflection; +using System.Linq; namespace JsonSubTypes { @@ -48,7 +48,46 @@ internal override Type GetFallbackSubType(Type type) public override bool CanConvert(Type objectType) { - return objectType == _baseType || ToTypeInfo(_baseType).IsAssignableFrom(ToTypeInfo(objectType)); + if (objectType == _baseType || ToTypeInfo(_baseType).IsAssignableFrom(ToTypeInfo(objectType))) + { + return true; + } + + return InheritsOrImplementsGeneric(objectType, _baseType); + } + + protected static bool InheritsOrImplementsGeneric(Type objectType, Type baseType) + { + // Cas générique ouvert : _baseType = Base<> et objectType = Base + if (ToTypeInfo(baseType).IsGenericTypeDefinition && ToTypeInfo(objectType).IsGenericType) + { + // Comparer la définition générique + if (objectType.GetGenericTypeDefinition() == baseType) + { + return true; + } + + // Optionnel : remonter la hiérarchie des bases + var current = ToTypeInfo(objectType).BaseType; + while (current != null) + { + if (ToTypeInfo(current).IsGenericType && current.GetGenericTypeDefinition() == baseType) + { + return true; + } + + current = ToTypeInfo(current).BaseType; + } + + // Optionnel : interfaces génériques + if (GetImplementedInterfaces(objectType) + .Any(iface => ToTypeInfo(iface).IsGenericType && iface.GetGenericTypeDefinition() == baseType)) + { + return true; + } + } + + return false; } } } From 8abaa9e6639366fdb8b39daf1dc2fa4381c5c8d9 Mon Sep 17 00:00:00 2001 From: manuc66 <12345678+username@users.noreply.github.com> Date: Sat, 15 Nov 2025 23:37:30 +0100 Subject: [PATCH 2/3] Add test for interface --- JsonSubTypes.Tests/GenericTests.cs | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/JsonSubTypes.Tests/GenericTests.cs b/JsonSubTypes.Tests/GenericTests.cs index cba4904..1b42a65 100644 --- a/JsonSubTypes.Tests/GenericTests.cs +++ b/JsonSubTypes.Tests/GenericTests.cs @@ -94,4 +94,48 @@ public void DeserializingSubTypeWithDateParsesCorrectly() Assert.AreEqual(42, @base.Value); } } + + [TestFixture] + public class GenericBaseInterfaceTests + { + interface IBase + { + T Value { get; set; } + + string Kind { get; } + } + + class Nested1 : IBase + { + public T Value { get; set; } + public string Kind => "1"; + } + + class Nested2: IBase + { + public T Value { get; set; } + public string Kind => "2"; + } + + [Test] + public void DeserializingSubTypeWithDateParsesCorrectly() + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(JsonSubtypesConverterBuilder + .Of(typeof(IBase<>), "Kind") // type property is only defined here + .RegisterSubtype(typeof(Nested1<>), "1") + .RegisterSubtype(typeof(Nested2<>), "2") + //.SerializeDiscriminatorProperty() // ask to serialize the type property + .Build()); + + var json = JsonConvert.SerializeObject(new Nested1 + { + Value = 42, + }, settings); // {"Kind":"1","Value":42} + + var @base = JsonConvert.DeserializeObject>(json, settings); // JsonSerializationException. Could not create an instance of type Base`1[System.Int32]. Type is an interface or abstract class and cannot be instantiated. Path 'Kind', line 1, position 8. + + Assert.AreEqual(42, @base.Value); + } + } } From 7d4a08b13f23176587282e588c5edbde298dbc3b Mon Sep 17 00:00:00 2001 From: manuc66 <12345678+username@users.noreply.github.com> Date: Sat, 15 Nov 2025 23:42:40 +0100 Subject: [PATCH 3/3] improve tests --- JsonSubTypes.Tests/GenericTests.cs | 40 ++++++++---------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/JsonSubTypes.Tests/GenericTests.cs b/JsonSubTypes.Tests/GenericTests.cs index 1b42a65..7684ed9 100644 --- a/JsonSubTypes.Tests/GenericTests.cs +++ b/JsonSubTypes.Tests/GenericTests.cs @@ -56,7 +56,13 @@ public void DeserializingSubTypeWithDateParsesCorrectly() [TestFixture] public class GenericBaseTests { - abstract class Base + interface IBase + { + T Value { get; set; } + + string Kind { get; } + } + abstract class Base : IBase { public T Value { get; set; } @@ -74,14 +80,13 @@ class Nested2: Base } [Test] - public void DeserializingSubTypeWithDateParsesCorrectly() + public void Deserialize_BaseConcreteSubtype_WithJsonSubtypes_OnAbstractBase_ReturnsNested1() { var settings = new JsonSerializerSettings(); settings.Converters.Add(JsonSubtypesConverterBuilder .Of(typeof(Base<>), "Kind") // type property is only defined here .RegisterSubtype(typeof(Nested1<>), "1") .RegisterSubtype(typeof(Nested2<>), "2") - //.SerializeDiscriminatorProperty() // ask to serialize the type property .Build()); var json = JsonConvert.SerializeObject(new Nested1 @@ -93,39 +98,15 @@ public void DeserializingSubTypeWithDateParsesCorrectly() Assert.AreEqual(42, @base.Value); } - } - - [TestFixture] - public class GenericBaseInterfaceTests - { - interface IBase - { - T Value { get; set; } - - string Kind { get; } - } - - class Nested1 : IBase - { - public T Value { get; set; } - public string Kind => "1"; - } - - class Nested2: IBase - { - public T Value { get; set; } - public string Kind => "2"; - } - + [Test] - public void DeserializingSubTypeWithDateParsesCorrectly() + public void Deserialize_InterfaceConcreteSubtype_WithJsonSubtypes_OnInterface_ReturnsNested1() { var settings = new JsonSerializerSettings(); settings.Converters.Add(JsonSubtypesConverterBuilder .Of(typeof(IBase<>), "Kind") // type property is only defined here .RegisterSubtype(typeof(Nested1<>), "1") .RegisterSubtype(typeof(Nested2<>), "2") - //.SerializeDiscriminatorProperty() // ask to serialize the type property .Build()); var json = JsonConvert.SerializeObject(new Nested1 @@ -138,4 +119,5 @@ public void DeserializingSubTypeWithDateParsesCorrectly() Assert.AreEqual(42, @base.Value); } } + }