反序列化字典<字符串,对象>在 C# 中使用枚举值 [英] Deserialize Dictionary&lt;string, object&gt; with enum values in C#

查看:15
本文介绍了反序列化字典<字符串,对象>在 C# 中使用枚举值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在 C# 中序列化/反序列化 Dictionary.对象可以是任何可序列化的东西.

I am trying to serialize/deserialize a Dictionary<string, object> in C#. Object can be anything that is serializable.

Json.NET 几乎可以工作,但如果字典中的值是 enum,反序列化是不正确的,因为它被反序列化为 long.TypeNameHandling.All 没有任何区别.

Json.NET almost works, but if a value within the dictionary is an enum, the deserialization is not correct, as it gets deserialized as a long. TypeNameHandling.All does not make any difference.

有没有其他快速的序列化库解决方案.结果不一定是 JSON,但必须是文本.

Is there any other fast solution to serialization library. The result does not have to be JSON, but must be text.

我对传递给字典的数据也没有影响.我只需要序列化和反序列化任何妨碍我的东西.

I also have no influence on the data that is passed to the dictionary. I just have to serialize and deserialize anything that comes into my way.

StringEnumConverter 没有帮助.数据被转换回Dictionary,因此解串器不知道序列化的值是enum.它把它当作一个对象,当反序列化时,StringEnumConverter 仍然是一个 string;它在没有转换器的情况下被反序列化为 long.JSON.NET 不保留枚举.

StringEnumConverter does not help. The data gets converted back to Dictionary<string, object>, so the deserializer does not know that the serialized value is an enum. It treats it like an object, with StringEnumConverter it remains a string when deserialized; it gets deserialized as a long without the converter. JSON.NET does not preserve the enum.

我想提供的解决方案是一个现有接口的实现,该接口被注入到我无法更改的现有解决方案中.

The solution i want to provide is an implementation of an existing interface that gets injected into an existing solution that i cannot change.

这是我正在尝试做的一个例子

Here is an example of what i am trying to do

public enum Foo { A, B, C }
public enum Bar { A, B, C }
public class Misc { public Foo Foo { get; set; } }


var dict = new Dictionary<string, object>();
dict.Add("a", Foo.A);
dict.Add("b", Bar.B);
dict.Add("c", new Misc());

// serialize dict to a string s
// deserialize s to a Dictionary<string, object> dict2

Assert.AreEqual(Foo.A, dict2["a"]);
Assert.AreEqual(Bar.B, dict2["b"]);

重要:我无法控制dict;它实际上是一个从 Dictionary 派生的自定义类型:我只需要确保反序列化时所有反序列化的键和值都来自同一类型,这样就不需要强制转换.再说一次,我不必使用 JSON;也许还有其他一些序列化程序可以处理这项工作!?

Important: i cannot control dict; it is actually a custom type that is derived from Dictionary<string, object>: I just have to make sure that all keys and values deserialized are from the same type when deserialized, so that no casting is needed. And again, i do not have to use JSON; maybe there is some other serializer that can handle the job!?

推荐答案

想必你已经在用 TypeNameHandling.All,它应该通过发出 "$type" 对象属性以及对象本身.不幸的是,对于诸如枚举(以及诸如 intlong 之类的其他类型)之类的类型,这不起作用,因为它们被序列化为 JSON 原语,没有机会包含一个 "$type" 属性.

Presumably you are already serializing your dictionary with TypeNameHandling.All, which should correctly serialize and deserialize the new Misc() value by emitting a "$type" object property along with the object itself. Unfortunately, for types such as enums (and others such as as int and long), this doesn't work because these are serialized as JSON primitives, with no opportunity to include a "$type" property.

解决方案是,在序列化具有 object 值的字典时,将对象包装器序列化为可以封装类型信息的原始值,如 这个答案.由于您无法修改任何传入的对象并且需要注入"适当的包装器,因此您可以使用应用适当的 项目转换器到字典值:

The solution is, when serializing a dictionary with object values, to serialize object wrappers for primitive values that can encapsulate the type information, along the lines of this answer. Since you cannot modify any of your incoming objects and need to "inject" the proper wrappers, you can do this with a custom contract resolver that applies an appropriate item converter to the dictionary values:

public class UntypedToTypedValueContractResolver : DefaultContractResolver
{
    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    // See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
    static UntypedToTypedValueContractResolver instance;

    // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
    static UntypedToTypedValueContractResolver() { instance = new UntypedToTypedValueContractResolver(); }

    public static UntypedToTypedValueContractResolver Instance { get { return instance; } }

    protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
    {
        var contract = base.CreateDictionaryContract(objectType);

        if (contract.DictionaryValueType == typeof(object) && contract.ItemConverter == null)
        {
            contract.ItemConverter = new UntypedToTypedValueConverter();
        }

        return contract;
    }
}

class UntypedToTypedValueConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var value = serializer.Deserialize(reader, objectType);
        if (value is TypeWrapper)
        {
            return ((TypeWrapper)value).ObjectValue;
        }
        return value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (serializer.TypeNameHandling == TypeNameHandling.None)
        {
            Debug.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
            serializer.Serialize(writer, value);
        }
        // Handle a couple of simple primitive cases where a type wrapper is not needed
        else if (value is string)
        {
            writer.WriteValue((string)value);
        }
        else if (value is bool)
        {
            writer.WriteValue((bool)value);
        }
        else
        {
            var contract = serializer.ContractResolver.ResolveContract(value.GetType());
            if (contract is JsonPrimitiveContract)
            {
                var wrapper = TypeWrapper.CreateWrapper(value);
                serializer.Serialize(writer, wrapper, typeof(object));
            }
            else
            {
                serializer.Serialize(writer, value);
            }
        }
    }
}

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

然后像这样使用它:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    ContractResolver = UntypedToTypedValueContractResolver.Instance,
    Converters = new [] { new StringEnumConverter() }, // If you prefer
};

var json = JsonConvert.SerializeObject(dict, Formatting.Indented, settings);

var dict2 = JsonConvert.DeserializeObject<Dictionary<string, object>>(json, settings);

示例 fiddle.

最后,在使用 TypeNameHandling 时,请注意 Newtonsoft 文档:

Finally, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:

当您的应用程序从外部源反序列化 JSON 时,应谨慎使用 TypeNameHandling.使用 None 以外的值反序列化时,应使用自定义 SerializationBinder 验证传入类型.

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.

有关为什么需要这样做的讨论,请参阅Newtonsoft Json 中的 TypeNameHandling 警告.

For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.

这篇关于反序列化字典<字符串,对象>在 C# 中使用枚举值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆