I would like to be able to ask JSON.net to deserialize a JSON string into an interface, and return one of the classes that implements that interface. Is that possible?
We tried to create our own JsonConverter, but got an exception with message "Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantiated." (from Newtonsoft.Json.Serialization.JsonSerializerInternalReader). It looks like JSON.net explicitly checks the type before it makes it to ReadJson()
override in our custom converter.
EDIT: added mcve
public interface IMessage { }
public class ErrorMessage : IMessage { public string message; }
public class WarnMessage : IMessage { public List<string> warnings; }
public class Messages {
public string id;
public List<IMessage> messages;
}
public class MessageJsonConverter : JsonConverter
{
private readonly ISet<Type> _types;
public MessageJsonConverter()
{
IEnumerable<Type> types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany( s => s.GetTypes() )
.Where( p => typeof( IMessage ).IsAssignableFrom( p ) )
.Where( p => p.IsClass );
_types = new HashSet<Type>( types );
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
// Never hits breakpoint here
if( !CanConvert( value.GetType() ) )
{
throw new ArgumentException( "Invalid type." );
}
JToken t = JToken.FromObject( value );
t.WriteTo( writer );
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
throw new NotImplementedException( "Can be done without a custom JsonConverter." );
}
public override bool CanConvert( Type objectType )
{
return _types.Any( t => t == objectType );
}
}
...
public class Program
{
public static void Main( string[] args )
{
IMessage errorMessage = new ErrorMessage() { message = "error" };
string errorMessageJson = JsonConvert.SerializeObject( errorMessage );
Console.Out.WriteLine( errorMessageJson );
// {"message":"error"}
// throws Newtonsoft.Json.JsonSerializationException
IMessage deserializedErrorMesage = JsonConvert.DeserializeObject<IMessage>(
errorMessageJson,
new MessageJsonConverter()
);
#region Interesting case that also fails
IMessage warnMessage = new WarnMessage() { warnings = new List<string>() { "warn 1", "warn 2" } };
Messages messages = new Messages() { id = "batch1", messages = new List<IMessage> { errorMessage, warnMessage } };
string messagesJson = JsonConvert.SerializeObject( messages );
// throws Newtonsoft.Json.JsonSerializationException
IMessage deserializedMessages = JsonConvert.DeserializeObject<IMessage>(
messagesJson,
new MessageJsonConverter()
);
#endregion
Console.In.Read();
}
}