Десериализовать определенный enum в system.enum в Json.Net
У меня есть довольно общий класс 'правила', который я использую для управления поведением механизма анализа, который я пишу:
public class Rule
{
/// <summary>
/// The general rule type.
/// </summary>
public RuleType RuleType { get; set; }
/// <summary>
/// The human-readable description of the rule.
/// </summary>
public string RuleDescription { get; set; }
/// <summary>
/// The integer magnitude of the rule, if applicable.
/// </summary>
public int? RuleInt { get; set; }
/// <summary>
/// The boolean sign associated with the rule, if applicable.
/// </summary>
public bool? RuleBool { get; set; }
/// <summary>
/// The enum flag associated with the rule, if applicable. CAN be null.
/// </summary>
public System.Enum RuleFlagEnum { get; set; }
/// <summary>
/// A dumping ground for any other random crap I've failed to account for at this point in time.
/// </summary>
public object RuleObject { get; set; }
}
RuleType - это конкретное перечисление, например:
public enum RuleType
{
Invalid,
ModifyDifficulty,
StrengthChange,
ColorChange,
SignChange
}
Используя Json.NET, он отлично сериализует и десериализует.
RuleEnum, однако, доставляет мне проблемы. Использует ли сериализация перечисления по умолчанию или сериализацию перечисления строк, конкретный тип перечисления не предоставляется. Таким образом, во время десериализации я остаюсь с System.Enum
и строковое значение, которое совершенно бесполезно.
Это пример сериализации, чтобы показать, о чем я говорю:
{
"RuleType": "SignChange",
"RuleDescription": "Strength 1 Inversion Gate",
"RuleInt": 1,
"RuleFlagEnum": "Negative"
}
RuleFlagEnum, в данном случае, ссылается на перечисление:
public enum SignChange
{
Zero,
Positive,
Negative
}
Я пытался использовать все TypeNameHandling
варианты внутри Json.NET. Они только помещают подсказки типа на объекты, что не помогает с RuleFlagEnum, так как это технически примитив.
Я бы очень хотел сохранить перечисление в System.Enum, чтобы мы могли загрузить любое произвольное перечисление для последующей интерпретации по типу правила, чтобы все это было более расширяемым. Это возможно?
1 ответ
Трудность здесь в том, что System.Enum
является абстрактным классом, поэтому невозможно десериализовать значение неизвестного конкретного типа в качестве такого типа. Скорее, нужно где-то иметь информацию о конкретном типе в JSON, однако Json.NET будет сериализовать enum
в виде строки или целого числа (в зависимости от того, является ли StringEnumConverter
применяется) - но не как объект, таким образом не оставляя возможности для полиморфизма "$type"
свойство будет добавлено.
Решение заключается в том, чтобы при сериализации сериализовать универсальный класс-обертку, который может передавать конкретную информацию о типе:
public abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
public sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Затем используйте serialize the wrapper при сериализации вашего класса:
/// <summary>
/// The enum flag associated with the rule, if applicable. CAN be null.
/// </summary>
[JsonIgnore]
public System.Enum RuleFlagEnum { get; set; }
[JsonProperty("RuleFlagEnum", TypeNameHandling = TypeNameHandling.All)]
TypeWrapper RuleFlagEnumValue
{
get
{
return RuleFlagEnum == null ? null : TypeWrapper.CreateWrapper(RuleFlagEnum);
}
set
{
if (value == null || value.ObjectValue == null)
RuleFlagEnum = null;
else
RuleFlagEnum = (Enum)value.ObjectValue;
}
}
Это создает JSON, как показано ниже:
{ "RuleType": "ModifyDifficulty", "RuleFlagEnum": { "$type": "Question31351262.TypeWrapper`1[[Question31351262.MyEnum, MyApp]], MyApp", "Value": "Two, Three" }, }