Skip to content
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
73 changes: 72 additions & 1 deletion JsonSubTypes.Tests/GenericTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -49,4 +50,74 @@ public void DeserializingSubTypeWithDateParsesCorrectly()
Console.WriteLine(result);
}
}



[TestFixture]
public class GenericBaseTests
{
interface IBase<T>
{
T Value { get; set; }

string Kind { get; }
}
abstract class Base<T> : IBase<T>
{
public T Value { get; set; }

public abstract string Kind { get; }
}

class Nested1<T> : Base<T>
{
public override string Kind => "1";
}

class Nested2<T>: Base<T>
{
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<int>
{
Value = 42,
}, settings); // {"Kind":"1","Value":42}

var @base = JsonConvert.DeserializeObject<Base<int>>(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<int>
{
Value = 42,
}, settings); // {"Kind":"1","Value":42}

var @base = JsonConvert.DeserializeObject<IBase<int>>(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);
}
}

}
15 changes: 15 additions & 0 deletions JsonSubTypes/JsonSubtypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -532,6 +538,15 @@ internal static TypeInfo ToTypeInfo(Type type)
#endif
}

internal static IEnumerable<Type> GetImplementedInterfaces(Type type)
{
#if (!NETSTANDARD1_3)
return type.GetInterfaces();
#else
return type?.GetTypeInfo().ImplementedInterfaces;
#endif
}

internal static Type ToType(TypeInfo typeInfo)
{
#if (!NETSTANDARD1_3)
Expand Down
11 changes: 10 additions & 1 deletion JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Expand Down Expand Up @@ -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)
Expand Down
43 changes: 41 additions & 2 deletions JsonSubTypes/JsonSubtypesConverter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Reflection;
using System.Linq;

namespace JsonSubTypes
{
Expand Down Expand Up @@ -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<int>
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;
}
}
}
Loading