I have below interface:
public interface IInterface<out M>
{
M Message { get; }
string Str { get; }
}
And its implementation:
public class Implementation<M> : IInterface<M>
{
public M Message;
public string Str;
public Implementation(M message, string str)
{
Message = message;
Str = str;
}
M IInterface<M>.Message => this.Message;
string IInterface<M>.Str => this.Str;
}
Here is a sample M class:
public class Sample
{
public int X;
}
Here is the sample JSON I pass from javascript client:
{ "Message" : { "X": 100 }, "Str" : "abc" }
Now there is some legacy/external code (that I can't change) which tries to deserialize the above JSON object using Json.Net using DeserializeObject<IInterface<Sample>>(js_object_string).
How can I write a JsonConverter for this IInterface interface that deals with its generic parameter M. Most of the solutions on internet only work with the types that are known at compile time.
I tried below code (that I don't understand fully) but the external code doesn't think the deserialized object is IInterface.
static class ReflectionHelper
{
public static IInterface<T> Get<T>()
{
var x = JsonConvert.DeserializeObject<T>(str);
IInterface<T> y = new Implementation<T>(x, "xyz");
return y;
}
}
class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IInterface<>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var w = Newtonsoft.Json.Linq.JObject.Load(reader);
var x = typeof(ReflectionHelper).GetMethod(nameof(ReflectionHelper.Get)).MakeGenericMethod(objectType.GetGenericArguments()[0]).Invoke(null, new object[] { });
return x;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // otherwise I get a circular dependency error.
serializer.Serialize(writer, value);
}
}
Your
MyConvertercan be written as follows:Then add it to
Convertersfor serialization and deserialization as follows:And if you really cannot change the call to
DeserializeObject<IInterface<Sample>>(js_object_string)at all, you can add your converter to Json.NET's global default settings for the current thread like so:Alternatively, you could apply
MyConverterdirectly toIInterface<out M>like so:But if you do, you must apply
NoConverterfrom this answer to How to deserialize generic interface to generic concrete type with Json.Net? toImplementation<M>to avoid a stack overflow exception:Notes:
By overriding
JsonConverter.CanWriteand returningfalsewe avoid the need to implementWriteJson().In
ReadJson()we determine the concrete type to deserialize by extracting the generic parameters from the incomingobjectType, which is required to beIInterface<M>for someM, and constructing a concrete typeImplementation<M>using the same generic parameters.Json.NET supports deserialization from a parameterized constructor as described in JSON.net: how to deserialize without using the default constructor?. Since your
Implementation<M>has a single parameterized constructor that meets the requirements described, it is invoked to deserialize your concrete class correctly.DefaultSettingsapplies to all calls toJsonConvertthroughout your application, from all threads, so you should determine whether modifying these settings is appropriate for your application.NoConvertermust be applied toImplementation<M>because, in the absence of a converter of its own, it will inheritMyConverterfromIInterface<out M>which will subsequently cause a recursive call toMyConverter.ReadJson()when deserializing the concrete type, resulting in a stack overflow or circular reference exception. (You can debug this yourself to confirm.)For other options to generate a "default" deserialization of the concrete class without using a converter, see JSON.Net throws StackOverflowException when using [JsonConvert()] or Call default JsonSerializer in a JsonConverter for certain value type arrays. Answers that suggest constructing a default instance of the concrete type and then populating it using
JsonSerializer.Populate()will not work for you because your concrete type does not have a default constructor.Demo fiddles for
DefaultSettingshere, and forMyConverter+NoConverterhere.