Reputation: 165
I have this situation:
public class Dto
{
public int TypeId { get; set; }
public IType Type { get; set; }
}
public class Type1 : IType
{
public string PropertyA { get; set; }
}
public class Type2 : IType
{
public int PropertyB { get; set; }
public bool PropertyC { get; set; }
}
public class MyController : ApiController
{
[HttpPost]
public IHttpActionResult Post(Dto dto)
{
}
}
How can I deserialize for the correct implementation of IType
interface, depending on the value of the TypeId
property?
I tried using JsonConverter
(following this example: https://gist.github.com/teamaton/bba69cf95b9e6766f231), but I can only specify one concrete type:
public class Dto
{
public int TypeId { get; set; }
[JsonConverter(typeof(ConcreteTypeConverter<Type1>)]
public IType Type { get; set; }
}
Upvotes: 0
Views: 962
Reputation: 10035
JsonConverter
is the correct way to go, however the ConcreteTypeConverter
isn't for your case.
Assume you need to determine which concrete type to create at runtime based on TypeId
, you will need a JsonConverter
on Dto
not on Type
property.
Try this:
[JsonConverter(typeof(DtoJsonConverter))]
public class Dto
{
public IType Type { get; set; }
public int TypeId { get; set; }
}
class DtoJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Dto);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// Load this to a JObject so that we can read TypeId
var obj = JObject.Load(reader);
var typeId = obj["TypeId"].Value<int>();
// Figure out JSON covnerter for type property based on TypeId
JsonConverter converter = null;
switch (typeId)
{
// Assuming 1 means Type1
case 1:
converter = new CreateITypeJsonConverter(() => new Type1());
break;
case 2:
converter = new CreateITypeJsonConverter(() => new Type2());
break;
}
if (converter != null)
{
serializer.Converters.Add(converter);
}
try
{
// Now create Dto and populate the object.
// This will call the JsonConverter we just added for Type property.
var dto = new Dto();
serializer.Populate(obj.CreateReader(), dto);
return dto;
}
finally
{
if (converter != null)
{
serializer.Converters.Remove(converter);
}
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
class CreateITypeJsonConverter : CustomCreationConverter<IType>
{
private readonly Func<IType> _factory;
public CreateITypeJsonConverter(Func<IType> factory)
{
_factory = factory;
}
public override IType Create(Type objectType)
{
return _factory();
}
}
The DtoJsonConverter
works out the concrete type of IType
as per the value of TypeId
, and use another CreateITypeJsonConverter
to instantiate the concrete type, then populate the Dto
.
It is also possible that you could move TypeId
into IType
, then use one of the methods in this question: JsonConverter with Interface
Upvotes: 2