【问题标题】:How to force JSON serialization to include type information for a polymorphic System.Enum member?如何强制 JSON 序列化包含多态 System.Enum 成员的类型信息?
【发布时间】:2017-06-13 21:54:38
【问题描述】:

我正在使用 JSON 序列化和反序列化来序列化/反序列化我的大型应用程序的存储类。 在我的应用程序中的某些地方,我需要在 Enum 或总和中保存不同 enumsType 的枚举,我需要将从父类继承的不同类保存到父类类型的变量中。请查看以下代码,该代码演示了枚举示例。在这些情况下,序列化会出错,尤其是对于枚举,并且由于枚举中的变量类型而无法找到枚举。例如在下面的示例代码中,inputEnum 是 Enum 类型,可以获取 Enum1 和 Enum2 的枚举。但在反序列化中,Json 带来了无法将 int64 转换为 Enum 类型的错误。 现在我需要 JSON 来保存变量的原始类型来反序列化它们。例如在inputenum=Enum1.hi 中,类型被保存为 Enum1 而不是现在发生的 Enum。 请给出你的解决方案。

    public enum Enum1
    {
        hi=0,
        hello
    }
    public enum Enum2
    {
        by = 0,
        goodbye
    }
    public class TUnitClass
    {
        public string Id { set; get; }
        public string Name { set; get; }
        public Enum inputenum { set; get; }
        public string Comment { set; get; }
        public string displayingString { set; get; }


    }
    private void Form1_Load(object sender, EventArgs e)
    {
        List<TUnitClass> cls1=new List<TUnitClass>()
        {
            new TUnitClass() {inputenum = Enum2.goodbye},
            new TUnitClass() {inputenum = Enum2.by},
            new TUnitClass() {inputenum = Enum1.hello},
            new TUnitClass() {inputenum = Enum1.hi},
            new TUnitClass() {inputenum = Enum2.goodbye},
        };
        string strSerialized = JsonConvert.SerializeObject(cls1);
        List<TUnitClass> cls2 = JsonConvert.DeserializeObject<List<TUnitClass>>(strSerialized,new JsonSerializerSettings() {Error = ErrorOnDeserialization});
    }
    private void ErrorOnDeserialization(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs e)
    {



    }

更新

我已尝试实施来自Deserialize specific enum into system.enum in Json.Net 的解决方案。序列化过程中类型信息正确写入 JSON 文件,但反序列化过程中抛出异常:

Newtonsoft.Json.JsonSerializationException occurred
  Message="Type specified in JSON 'MTC.Utility.UnitConvertor.TypeWrapper`1[[MTC.Utility.UnitConvertor.EAcceleration, MTC.Utility.UnitConvertor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], MTC.Utility.UnitConvertor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with 'System.Enum, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Path 'listOfUnitSystems[0].UnitsList[0].Unit.$type', line 17, position 269."
  Source="Newtonsoft.Json"
  StackTrace:
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolveTypeName(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, String qualifiedTypeName) in C:\Development\Releases\Json\Working\Newtonsoft.Json\Working-Signed\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalReader.cs:line 814
  InnerException: 

这是更新后的代码:

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

public class TUnitClass
{
    public string Id { set; get; }
    public string Name { set; get; }
    public EQuantities Quantity { set; get; }
    [JsonIgnore]
    public System.Enum Unit { get; set; }

    [JsonProperty("Unit", TypeNameHandling = TypeNameHandling.All)]
    TypeWrapper UnitValue
    {
        get
        {
            return Unit == null ? null : TypeWrapper.CreateWrapper(Unit);
        }
        set
        {
            if (value == null || value.ObjectValue == null)
                Unit = null;
            else
                Unit = (Enum)value.ObjectValue;
        }
    }
    public string Comment { set; get; }
    public string displayingString { set; get; }
    public TUnitClass(string id, EQuantities quantity,Enum unit, string comment)
    {
        Id = id;
        Name = quantity.ToString();
        Quantity = quantity;
        Unit = unit;
        Comment = comment;

        FieldInfo fi = unit.GetType().GetField(unit.ToString());
        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        displayingString= attributes[0].Description;
    }
}

更新 2

最后,如果我有一个Enum[] 属性而不是Enum,我该如何处理这个问题?

【问题讨论】:

  • 我已经使用了该方法,但是我在反序列化时遇到了一个问题,它带来了 null 但是序列化的字符串不为 null。!反序列化也不会带来任何错误
  • 好的,那么您能否edit 您的问题以显示无效的尝试,或者添加一个dotnetfiddle.net 来演示链接解决方案的问题?
  • 你从错误的角度开始。不要使用 Json.Net 序列化任何东西,并且会对与世界其他地方设计数据合约的结果感到惊讶。从合同设计以及如何共享数据开始。之后在代码中实现这个合约
  • 嗨 Rufo 先生,您能否提供一个链接来解释您的解决方案?谢谢

标签: c# json serialization enums json.net


【解决方案1】:

question 31351262 的解决方案不起作用的原因是您的类型TUnitClass 没有默认构造函数。相反,它有一个参数化的构造函数。在这种情况下,Json.NET 将调用该构造函数,将 JSON 对象按名称匹配到构造函数参数,模大小写。 (有关详细信息,请参阅 this answer。)不幸的是,您的构造函数将抽象 System.Enum 作为其 unit 参数:

public TUnitClass(string id, EQuantities quantity, Enum unit, string comment)
{
}

Json.NET 将正确反序列化代理 TypeWrapper&lt;TEnum&gt;,然后尝试将其强制转换为 System.Enum 以调用构造函数,然后失败,抛出您看到的异常。

解决方案是添加一个接受代理TypeWrapper unit 值的构造函数,并用[JsonConstructor] 标记该构造函数。只要标有[JsonConstructor],它就可以是私有的。以下应该有效:

public class TUnitClass
{
    [JsonConstructor]
    TUnitClass(string id, EQuantities quantity, TypeWrapper<Enum> unit, string comment)
        : this(id, quantity, unit.Value(), comment)
    {
    }

    public TUnitClass(string id, EQuantities quantity, Enum unit, string comment)
    {
        Id = id;
        Name = quantity.ToString();
        Quantity = quantity;
        Unit = unit;
        Comment = comment;

        FieldInfo fi = unit.GetType().GetField(unit.ToString());
        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        displayingString = attributes[0].Description;
    }

    public string Id { set; get; }
    public string Name { set; get; }
    public EQuantities Quantity { set; get; }

    [JsonIgnore]
    public System.Enum Unit { get; set; }

    [JsonProperty("Unit", TypeNameHandling = TypeNameHandling.All)]
    TypeWrapper<Enum> UnitSurrogate
    {
        get
        {
            return Unit == null ? null : TypeWrapper<Enum>.CreateWrapper(Unit);
        }
        set
        {
            Unit = value.Value();
        }
    }

    public string Comment { set; get; }
    public string displayingString { set; get; }

    public TUnitClass Copy()
    {
        TUnitClass copiedUnitClass=new TUnitClass(Id,Quantity,Unit,Comment);
        return copiedUnitClass;
    }
}

public abstract class TypeWrapper<TBase>
{
    protected TypeWrapper() { }

    [JsonIgnore]
    public abstract TBase BaseValue { get; }

    public static TypeWrapper<TBase> CreateWrapper<TDerived>(TDerived value) where TDerived : TBase
    {
        if (value == null)
            return null;
        var type = value.GetType();
        if (type == typeof(TDerived))
            return new TypeWrapper<TDerived, TBase>(value);
        // Return actual type of subclass
        return (TypeWrapper<TBase>)Activator.CreateInstance(typeof(TypeWrapper<,>).MakeGenericType(type, typeof(TBase)), value);
    }
}

public static class TypeWrapperExtensions
{
    public static TBase Value<TBase>(this TypeWrapper<TBase> wrapper)
    {
        if (wrapper == null || wrapper.BaseValue == null)
            return default(TBase);
        return wrapper.BaseValue;
    }
}

public sealed class TypeWrapper<TDerived, TBase> : TypeWrapper<TBase> where TDerived : TBase
{
    public TypeWrapper() : base() { }

    public TypeWrapper(TDerived value)
        : base()
    {
        this.Value = value;
    }

    public override TBase BaseValue { get { return Value; } }

    public TDerived Value { get; set; }
}

TypeNameAssemblyFormat = FormatterAssemblyStyle.Full 应该不再需要了。

注意我修改了链接的答案以添加类型安全性以确保序列化类型实际上是System.Enum,这应该通过在反序列化期间防止意外类型来提高安全性。有关TypeNameHandling 安全性的一般性讨论,请参阅TypeNameHandling caution in Newtonsoft Json

更新

如果我有 Enum[] 而不是 Enum, 我该如何处理这个问题?

您需要使用 TypeWrapper&lt;Enum&gt; 对象的代理数组,如下所示:

public class TUnitArrayClass
{
    [JsonConstructor]
    TUnitArrayClass(TypeWrapper<Enum>[] units)
        : this(units == null ? null : units.Select(s => s.Value()).ToArray())
    {
    }

    public TUnitArrayClass(IEnumerable<System.Enum> units)
    {
        this.Units = (units ?? Enumerable.Empty<System.Enum>()).ToArray();
    }

    [JsonIgnore]
    public System.Enum[] Units { get; set; }

    [JsonProperty("Unit", TypeNameHandling = TypeNameHandling.All)]
    TypeWrapper<Enum> [] UnitsSurrogate
    {
        get
        {
            return Units == null ? null : Units.Select(u => TypeWrapper<Enum>.CreateWrapper(u)).ToArray();
        }
        set
        {
            if (value == null)
                Units = null;
            else
                Units = value.Select(s => s.Value()).ToArray();
        }
    }
}

【讨论】:

  • 我认为标题不清楚。请按照您所知道的进行编辑。
  • 非常感谢。由于这种巨大的干扰,我感到非常可耻。另一个问题。如果我有一个 Enum[] 而不是 Enum,我该如何处理这个问题?我使用了这段代码,但它并没有解决问题。当我调查序列化字符串时,我发现类型包装器已将类型区分为 Enum[]!
  • [JsonIgnore] public Enum[] Units { get;放; } [JsonProperty("Units", TypeNameHandling = TypeNameHandling.All)] private TypeWrapper UnitsValue { get { return Units == null ? null : TypeWrapper.CreateWrapper(Units); } 设置 { 单位 = value.Value(); } }
  • 您需要添加一个代理 TypeWrapper&lt;Enum&gt; [] UnitValues,它返回一个类型包装对象数组。
  • 我不知道该怎么感谢你。它工作得非常好。再次感谢您。
猜你喜欢
  • 2021-11-01
  • 2013-10-18
  • 2015-07-20
  • 2010-10-04
  • 1970-01-01
相关资源
最近更新 更多