Десериализовать определенный 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"
  },
}
Другие вопросы по тегам