diff --git a/JsonSubTypes.Tests/GenericTests.cs b/JsonSubTypes.Tests/GenericTests.cs index 9eaf8f4..7684ed9 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,74 @@ public void DeserializingSubTypeWithDateParsesCorrectly() Console.WriteLine(result); } } + + + + [TestFixture] + public class GenericBaseTests + { + interface IBase + { + T Value { get; set; } + + string Kind { get; } + } + abstract class Base : IBase + { + 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 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") + .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); + } + + [Test] + 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") + .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; } } }