简体   繁体   English

反序列化字典 <string, object> 在C#中具有枚举值

[英]Deserialize Dictionary<string, object> with enum values in C#

I am trying to serialize/deserialize a Dictionary<string, object> in C#. 我正在尝试在C#中序列化/反序列化Dictionary<string, object> Object can be anything that is serializable. 对象可以是任何可序列化的对象。

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 . Json.NET几乎可以工作,但是如果字典中的值是enum ,则反序列化是不正确的,因为它会反序列化为long TypeNameHandling.All does not make any difference. TypeNameHandling.All没有任何区别。

Is there any other fast solution to serialization library. 序列化库是否有其他快速解决方案。 The result does not have to be JSON, but must be text. 结果不必是JSON,而必须是文本。

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. 我只需要对遇到的任何问题进行序列化和反序列化。

EDIT: StringEnumConverter does not help. 编辑: StringEnumConverter没有帮助。 The data gets converted back to Dictionary<string, object> , so the deserializer does not know that the serialized value is an enum . 数据被转换回Dictionary<string, object> ,因此解串器不知道序列化的值是enum It treats it like an object, with StringEnumConverter it remains a string when deserialized; 它像对待对象一样对待它,使用StringEnumConverter可以在反序列化时保留string it gets deserialized as a long without the converter. 如果没有转换器,它将反序列化很long JSON.NET does not preserve the enum. JSON.NET不会保留枚举。

The solution i want to provide is an implementation of an existing interface that gets injected into an existing solution that i cannot change. 我想提供的解决方案是现有接口的实现,该接口被注入到我无法更改的现有解决方案中。

EDIT2: Here is an example of what i am trying to do EDIT2:这是我正在尝试做的一个例子

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"]);

Important: i cannot control dict ; 重要提示:我无法控制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. 它实际上是从Dictionary<string, object>派生的自定义类型:我只需要确保反序列化时所有反序列化的键和值都来自同一类型,因此不需要强制转换。 And again, i do not have to use JSON; 再说一次,我不必使用JSON; maybe there is some other serializer that can handle the job!? 也许还有其他一些串行器可以处理这项工作!

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. 大概您已经使用TypeNameHandling.All序列化字典,该字典应通过与对象本身一起发出"$type"对象属性来正确地序列化和反序列化new Misc()值。 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. 不幸的是,对于枚举之类的类型(以及诸如intlong其他类型),这是行不通的,因为它们已序列化为JSON原语,没有机会包含"$type"属性。

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 . 解决方案是,在用object值序列化字典时,按照此答案的方法 ,序列化可包装类型信息的原始值的对象包装器。 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; }
}

Then use it like: 然后像这样使用它:

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

Sample fiddle . 样品提琴

Finally, when using TypeNameHandling , do take note of this caution from the Newtonsoft docs : 最后,在使用TypeNameHandling ,请注意Newtonsoft docs中的此警告:

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. 当您的应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling。 Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None. 反序列化除None以外的其他值时,应使用自定义SerializationBinder验证传入的类型。

For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json . 有关为什么可能需要这样做的讨论,请参见Newtonsoft Json中的TypeNameHandling注意事项

假设您有权修改对象类,则可以将JsonCoverter属性添加到该类的枚举成员。

[JsonConverter(typeof(StringEnumConverter))]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM