Deserialize Dictionary <string, object> с значениями перечисления в С#

Я пытаюсь сериализовать/десериализовать Dictionary<string, object> в С#. Объект может быть сериализуемым.

Json.NET почти работает, но если значение в словаре составляет enum, десериализация неверна, поскольку он десериализуется как long. TypeNameHandling.All не имеет никакого значения.

Есть ли еще одно быстрое решение для библиотеки сериализации. Результат не должен быть JSON, но должен быть текстом.

Я также не влияю на данные, которые передаются в словарь. Мне просто нужно сериализовать и десериализировать все, что приходит мне на ум.

EDIT: StringEnumConverter не помогает. Данные преобразуются обратно в Dictionary<string, object>, поэтому десериализатор не знает, что сериализованное значение равно 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<string, object>: я просто должен убедиться, что все дескрипторы и десериализованные ключи из одного и того же типа десериализованы, так что никакого литья не требуется. И снова, я не должен использовать JSON; может быть, есть и другой сериализатор, который может справиться с работой!?

4
12 июля '16 в 21:26
источник поделиться
2 ответов

Предположительно, вы уже сериализуете словарь с помощью TypeNameHandling.All, который должен правильно сериализовать и десериализовать значение new Misc(), испустив < свойство href= "http://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm" rel= "nofollow noreferrer" > "$type" вместе с самим объектом. К сожалению, для таких типов, как перечисления (и другие, такие как int и long), это не работает, потому что они сериализованы как примитивы JSON, без возможности включить свойство "$type".

Решение при сериализации словаря со значениями object позволяет сериализовать обертки объектов для примитивных значений, которые могут инкапсулировать информацию о типе по строкам этого ответа. Поскольку вы не можете изменять какие-либо из ваших входящих объектов и должны "вводить" надлежащие обертки, вы можете сделать это с помощью настраиваемого разрешающего устройства, которое применяет соответствующий элементный конвертер до значений словаря:

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:

TypeNameHandling следует использовать с осторожностью, когда ваше приложение десериализует JSON из внешнего источника. Входящие типы должны быть проверены с помощью специального SerializationBinder при десериализации со значением, отличным от None.

Для обсуждения того, почему это может быть необходимо, см. TypeNameHandling осторожность в Newtonsoft Json.

3
13 июля '16 в 2:27
источник

Связанные вопросы


Похожие вопросы

Предполагая, что у вас есть доступ к модификации класса object, вы можете добавить атрибут JsonCoverter к члену перечисления класса.

[JsonConverter(typeof(StringEnumConverter))]
0
12 июля '16 в 21:33
источник

Посмотрите другие вопросы по меткам или Задайте вопрос