Как я могу заставить Json.NET сериализовать и десериализовать объявленные свойства пользовательских динамических типов, которые также реализуют IDictionary <string, object>? - PullRequest
1 голос
/ 02 апреля 2019

У меня есть пользовательский тип, полученный из типа DynamicObject.Этот тип имеет фиксированные свойства, объявленные в типе.Таким образом, он позволяет пользователю предоставлять некоторые необходимые свойства в дополнение к любым динамическим свойствам, которые они хотят.Когда я использую метод JsonConvert.DeserializeObject<MyType>(json) для десериализации данных для этого типа, он не устанавливает объявленные свойства, но эти свойства доступны через свойство индексатора объекта в динамическом объекте.Это говорит мне о том, что он просто обрабатывает объект как словарь и не пытается вызвать объявленные установщики свойств, а также не использует их для вывода информации о типе свойства.

Кто-нибудь сталкивался с такой ситуацией раньше?Любая идея, как я могу поручить классу JsonConvert учитывать объявленные свойства при десериализации данных объекта?

Я пытался использовать пользовательский JsonConverter, но для этого нужно написать сложное чтение JSONи методы письма.Я надеялся найти способ внедрить информацию о контракте свойства, переопределив JsonContractResolver или JsonConverter и т. Д.это делает для обычных типов.Но кажется, что он просто обрабатывает объект как обычный словарь.

Обратите внимание, что:

  • Я не могу удалить интерфейс IDictionary<string, object>, поскольку некоторые изслучаи в моем API полагаются на объект, чтобы быть словарем, а не динамическим.

  • Добавление [JsonProperty] ко всем объявленным свойствам для сериализации нецелесообразно, поскольку его производные типы создаются другими разработчиками и им не нужно явно заботиться о механизме сохранения.

Какие-либо предложения о том, как я могу заставить его работать правильно?

Ответы [ 2 ]

0 голосов
/ 02 апреля 2019

У вас есть несколько проблем здесь:

  1. Вам необходимо правильно переопределить DynamicObject.GetDynamicMemberNames(), как описано в в этом ответе на Сериализация экземпляра класса, производного от класса DynamicObject по АльбертК для Json.NET, чтобы иметь возможность сериализовать ваши динамические свойства.

    (Это уже исправлено в отредактированной версии вашего вопроса.)

  2. Объявленные свойства не отображаются, если вы явно не пометите их как [JsonProperty] (как объяснено в этот ответ до C # Как сериализовать (JSON, XML) нормальный свойства класса, который наследуется от DynamicObject ), но определения типов доступны только для чтения и не могут быть изменены.

    Проблема здесь заключается в том, что JsonSerializerInternalWriter.SerializeDynamic() только сериализует объявленные свойства, для которых JsonProperty.HasMemberAttribute == true. (Я не знаю, почему эта проверка делается там, было бы более разумно установить CanRead или Ignored внутри решателя контракта.)

  3. Вы хотели бы, чтобы ваш класс реализовал IDictionary<string, object>, но если вы это сделаете, это нарушит десериализацию; объявленные свойства больше не заполняются, а добавляются в словарь.

    Проблема здесь заключается в том, что DefaultContractResolver.CreateContract() возвращает JsonDictionaryContract вместо JsonDynamicContract, когда входящий тип реализует IDictionary<TKey, TValue> для любых TKey и TValue.

Предполагая, что у вас исправлена ​​проблема № 1, проблемы № 2 и № 3 могут быть обработаны с помощью пользовательского обработчика контрактов , например следующего:

public class MyContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        // Prefer JsonDynamicContract for MyDynamicObject
        if (typeof(MyDynamicObject).IsAssignableFrom(objectType))
        {
            return CreateDynamicContract(objectType);
        }
        return base.CreateContract(objectType);
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        // If object type is a subclass of MyDynamicObject and the property is declared
        // in a subclass of MyDynamicObject, assume it is marked with JsonProperty 
        // (unless it is explicitly ignored).  By checking IsSubclassOf we ensure that 
        // "bookkeeping" properties like Count, Keys and Values are not serialized.
        if (type.IsSubclassOf(typeof(MyDynamicObject)) && memberSerialization == MemberSerialization.OptOut)
        {
            foreach (var property in properties)
            {
                if (!property.Ignored && property.DeclaringType.IsSubclassOf(typeof(MyDynamicObject)))
                {
                    property.HasMemberAttribute = true;
                }
            }
        }
        return properties;
    }
}

Затем, чтобы использовать обработчик контракта, кеш это где-то для производительности:

static IContractResolver resolver = new MyContractResolver();

А затем сделайте:

var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
};
string json = JsonConvert.SerializeObject(obj, settings);

Образец скрипки здесь .

0 голосов
/ 02 апреля 2019

Я не могу сказать, что находится внутри класса ProxyInfo.Однако при использовании строки для свойства Name и Proxy десериализация работает правильно.Пожалуйста, проверьте следующий рабочий образец:

    class Program
    {
        static void Main(string[] args)
        {
            // NOTE: This is how I load the JSON data into the new type.
            var obj = JsonConvert.DeserializeObject<MyCustomDynamicObject>("{name:'name1', proxy:'string'}");
            var proxy = obj.Proxy;
            var name = obj.Name;
        }
    }

    public class MyDynamicObject : DynamicObject
    {
        // Implements the functionality to store dynamic properties in 
        // dictionary.
        // NOTE: This base class does not have any declared properties.
    }

    // NOTE: This is the actual concrete type that has declared properties
    public class MyCustomDynamicObject : MyDynamicObject
    {
        public string Name { get; set; }
        public string Proxy { get; set; }
    }
...