У меня были похожие требования, вот как я это сделал:
- Создание собственного класса JsonConverter
- Скажите сериализатору использовать ваш собственный JsonConverter
JsonConverterПример
Обратите внимание, что это альфа-код, и вам придется менять части, особенно.GetAllItemTypes, который инициализирует ключ типа для сопоставления типа (известное ограничение: нуждается в блокировке).
public class TypePropertyConverter : JsonConverter
{
/// <summary>
/// During write, we have to return CanConvert = false to be able to user FromObject internally w/o "self referencing loop" errors.
/// </summary>
private bool _isInWrite = false;
public override bool CanWrite => !_isInWrite;
private static Dictionary<string, Type> _allItemTypes;
public static Dictionary<string, Type> AllItemTypes => _allItemTypes ?? (_allItemTypes = GetAllItemTypes());
/// <summary>
/// Read all types with JsonType or BsonDiscriminator attribute from current assembly.
/// </summary>
/// <returns></returns>
public static Dictionary<string, Type> GetAllItemTypes()
{
var allTypesFromApiAndCore = typeof(TypePropertyConverter)
.Assembly
.GetTypes()
.Concat(typeof(OrdersCoreRegistry)
.Assembly
.GetTypes());
var dict = new Dictionary<string, Type>();
foreach (var type in allTypesFromApiAndCore)
{
if (type.GetCustomAttributes(false).FirstOrDefault(a => a is JsonTypeAttribute) is JsonTypeAttribute attr)
{
dict.Add(attr.TypeName, type);
}
else if (type.GetCustomAttributes(false).FirstOrDefault(a => a is BsonDiscriminatorAttribute) is BsonDiscriminatorAttribute bda)
{
dict.Add(bda.Discriminator, type);
}
}
return dict;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_isInWrite = true;
try
{
var type = value.GetType();
var typeKey = AllItemTypes.First(kv => kv.Value == type).Key;
var jObj = JObject.FromObject(value, serializer);
jObj.AddFirst(new JProperty("type", typeKey));
jObj.WriteTo(writer);
}
finally
{
_isInWrite = false;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
// we need to read and remove the "type" property first
var obj = JObject.Load(reader);
var typeKey = obj["type"];
if (typeKey == null)
{
throw new InvalidOperationException("Cannot deserialize object w/o 'type' property.");
}
obj.Remove("type");
// create object
if (!AllItemTypes.TryGetValue(typeKey.Value<string>(), out var type))
{
throw new InvalidOperationException($"No type registered for key '{typeKey}'. Annotate class with JsonType attribute.");
}
var contract = serializer.ContractResolver.ResolveContract(type);
var value = contract.DefaultCreator();
if (value == null)
{
throw new JsonSerializationException("No object created.");
}
using (var subReader = obj.CreateReader())
{
serializer.Populate(subReader, value);
}
return value;
}
public override bool CanConvert(Type objectType)
{
return AllItemTypes.Any(t => t.Value == objectType);
}
}
Он ищет настраиваемый атрибут "JsonType" и будет использовать значение своего свойства Name в качестве ключа.Если JsonType не найден, он будет искать атрибут BsonDiscriminator (из mongodb) как запасной вариант.Вам придется настроить эту часть.
Рассказать Serializer о вас JsonConverter
Существует несколько способов сделать это.Я использую такие атрибуты, как:
Использовать конвертер для элементов списка:
[JsonProperty(ItemConverterType = typeof(TypePropertyConverter))]
public List<PipelineTrigger> Triggers { get; set; }
Подробнее см. https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_JsonProperty.htm.
Или вы могли быдобавить атрибут JsonConverter к базовому классу: https://www.newtonsoft.com/json/help/html/JsonConverterAttributeClass.htm