【问题标题】:Deserialize Dictionary<string, object> with enum values in C#在 C# 中使用枚举值反序列化 Dictionary<string, object>
【发布时间】:2016-07-12 18:26:34
【问题描述】:

我正在尝试在 C# 中序列化/反序列化 Dictionary&lt;string, object&gt;。 对象可以是任何可序列化的对象。

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

序列化库是否有其他快速解决方案。结果不必是 JSON,但必须是文本。

我对传递给字典的数据也没有影响。我只需要序列化和反序列化遇到的任何事情。

编辑:StringEnumConverter 没有帮助。数据被转换回Dictionary&lt;string, object&gt;,因此反序列化器不知道序列化值是enum。它将它视为一个对象,StringEnumConverter 在反序列化时仍然是string;它在没有转换器的情况下被反序列化为long。 JSON.NET 不保留枚举。

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

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

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

【问题讨论】:

  • 因为枚举默认是整数,所以它会被序列化为一个数字,但如果你也可以控制反序列化,那么它应该是自动转换
  • 当您说您无法控制数据时,是否意味着您无法向枚举添加属性,例如[JsonConverter(typeof(StringEnumConverter))]
  • TypeNameHandling 不适用于枚举,因为它们被序列化为字符串或数字,而不是对象,因此没有机会包含 "$type"。您需要将它们包装在某种东西中以传达类型,请参阅Deserialize specific enum into system.enum in Json.Net 以获得这样的包装器。至于如何将其注入您现有的解决方案,请分享一些示例代码。自定义合约解析器或转换器可能是一种可能性。

标签: c# serialization enums


【解决方案1】:

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

解决方案是,在使用object 值序列化字典时,按照this answer 的行序列化可以封装类型信息的原始值的对象包装器。由于您无法修改任何传入对象并且需要“注入”适当的包装器,因此您可以使用自定义合约解析器来执行此操作,该解析器将适当的 item converter 应用于字典值:

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 docs 的注意事项:

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

关于为什么这可能是必要的讨论,请参阅TypeNameHandling caution in Newtonsoft Json

【讨论】:

    【解决方案2】:

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

    [JsonConverter(typeof(StringEnumConverter))]
    

    【讨论】:

    • 不,我不知道。我实现了一个接口,它通过依赖注入注入到一个封闭的应用程序中。
    • 你能用你的例子更新你的帖子吗?
    猜你喜欢
    • 1970-01-01
    • 2017-06-12
    • 1970-01-01
    • 1970-01-01
    • 2015-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多