3

I have a fairly generic 'rule' class that I am using to drive the behavior of an analysis engine I'm writing:

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 is a specific enum, like so:

public enum RuleType
{
    Invalid,
    ModifyDifficulty,
    StrengthChange,
    ColorChange,
    SignChange
}

Using Json.NET, that both serializes and deserializes just fine.

RuleEnum, however, is giving me problems. Whether using the default enum serialization or the string enum serialization, the specific type of enum is not provided. As such, during deserialization, I am left with System.Enum and a string value, which is wholly unhelpful.

This is an example of the serialization, to show what I'm talking about:

{
   "RuleType": "SignChange",
   "RuleDescription": "Strength 1 Inversion Gate",
   "RuleInt": 1,
   "RuleFlagEnum": "Negative"
}

RuleFlagEnum, in this case, is referring to the enum:

public enum SignChange
{
    Zero,
    Positive,
    Negative
}

I have tried using all of the TypeNameHandling options inside Json.NET. They only put type hinting on the objects, which doesn't help with RuleFlagEnum since it is technically a primitive.

I would really, really like to keep the enum at System.Enum so we can load any arbitrary enum in for later interpretation by the rule type, so the entire thing is more expandable. Is this possible?

tmesser
  • 7,558
  • 2
  • 26
  • 38
  • Do you mean that you would like the output to be `"RuleFlagEnum: "SignChange.Negative"` instead? – cbr Jul 10 '15 at 23:26
  • @cubrr: If that means Json.NET can deserialize it properly without having to write a fully custom deserialization handler, yes! – tmesser Jul 10 '15 at 23:36
  • @OrelEraki: It absolutely is not if you read the question. I am already serializing the enum as a string. My problem is DEserialization from that string as part of an object hierarchy that is correct, but missing a critical bit of data to make it work properly. – tmesser Jul 10 '15 at 23:36
  • @YYY You're probably going to have to write a custom JsonConverter at least. – cbr Jul 10 '15 at 23:40
  • @cubrr: A JsonConverter to handle this one thing is fine, it's kind of an edge case so I wasn't expecting the framework to handle it out of the box. I'm not clear on how such a converter would be modeled, though. If you have an idea on how that would be set up, I'd love to see it in an answer! – tmesser Jul 10 '15 at 23:43
  • @YYY I'm really not familiar with creating custom JsonConverters, but I would imagine that you could just write `enumValue.GetType().ToString() + enumValue` to the JSON value and when reading, do something like `string[] vals = jsonValue.Split('.'); var enumVal = Emum.Parse(Type.GetType(vals[0]), vals[1]);` – cbr Jul 10 '15 at 23:50
  • You say `RuleType` but show declaration of `SkillCheckRuleType`, You say `RuleEnum` which is not mentioned anywhere else. So your question isn't clear. How about posting a minimalist but *complete* code (including your serialization/deserialization code and excluding unnecessary comments properties and methods) – EZI Jul 11 '15 at 00:10
  • @EZI Minor copy/paste error, fixed it. The code example is complete otherwise, and the comments and such, I felt, were quite helpful in showing why I am doing what I'm doing - it's specifically to be of broad use, and not all of these things get serialized. Also, would you care to explain why you feel this is a duplicate of a question that doesn't even ask for the same use case? – tmesser Jul 11 '15 at 05:42
  • 2
    For the others who marked this as a duple: I would also like to hear why this is a duplicate of a question that I am clearly already implementing the 'answer' to. It is not even about serialization as such. It is about DEserialization, and if the serialization strategy must change to allow for appropriate deserialization, then so be it. – tmesser Jul 11 '15 at 05:45

1 Answers1

2

The difficulty here is that System.Enum is an abstract class, so it is impossible to deserialize a value of unknown concrete type as such a type. Rather, one needs to have the specific type information in the JSON somewhere, however Json.NET will serialize an enum as a string or an integer (depending upon whether a StringEnumConverter is applied) -- but not an as an object, thus leaving no opportunity for a polymorphic "$type" property to be added.

The solution is, when serializing, to serialize a generic wrapper class that can convey the concrete type information:

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; }
}

Then use serialize the wrapper when serializing your class:

    /// <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;
        }
    }

This produces JSON like the following:

{
  "RuleType": "ModifyDifficulty",
  "RuleFlagEnum": {
    "$type": "Question31351262.TypeWrapper`1[[Question31351262.MyEnum, MyApp]], MyApp",
    "Value": "Two, Three"
  },
}
dbc
  • 104,963
  • 20
  • 228
  • 340