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?

asked Jul 10, 2015 at 22:26
9
  • Do you mean that you would like the output to be "RuleFlagEnum: "SignChange.Negative" instead? Commented Jul 10, 2015 at 23:26
  • @cubrr: If that means Json.NET can deserialize it properly without having to write a fully custom deserialization handler, yes! Commented Jul 10, 2015 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. Commented Jul 10, 2015 at 23:36
  • @YYY You're probably going to have to write a custom JsonConverter at least. Commented Jul 10, 2015 at 23:40
  • 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. Commented Jul 11, 2015 at 5:45

1 Answer 1

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"
 },
}
answered Jul 11, 2015 at 3:35

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.