Усиление эффектов `JsonConstructor` без редактирования класса

#c# #json.net

#c# #json.net

Вопрос:

У меня есть несколько классов со свойствами и конструкторами только для чтения, например:

 public class MyClass
{
    public string Foo { get; }
    public bool Bar { get; }
    public MyClass(string foo);
    public MyClass(string foo, bool bar);
}
  

Который будет сериализован, но DeserializeObject завершится неудачей.

Newtonsoft.Json.Исключение JsonSerializationException : не удается найти конструктор для типа MyClass. Класс должен иметь либо конструктор по умолчанию, один конструктор с аргументами, либо конструктор, помеченный атрибутом JsonConstructor. Путь ‘…’, строка 1, позиция 1

Добавление JsonConstructor атрибута будет работать, но иногда я не контролирую исходный код типов. Могу ли я указать конвертеру действовать так, как если бы атрибут был каким-то другим способом?

В идеале без выполнения каждого параметра вручную в a JsonConverter.ReadJson для каждого рассматриваемого типа.

Комментарии:

1. У меня нет ответа на ваш вопрос, но в качестве обходного пути — ничто не заставляет вас десериализовать этот конкретный тип. Вы могли бы также десериализовать свой собственный тип или даже анонимный тип, а затем сопоставить результаты с фактическим целевым типом. Конечно, для этого потребуется написать некоторый код, но также и пользовательский конвертер json…

2. Ну, это очень похоже на выполнение каждого параметра для каждого типа… Вероятно, я мог бы написать общее решение с помощью отражения (я предполагаю, что Json. Net все равно делает внутренне). Но если вы можете сказать «используйте этот конструктор typeA, typeB » или «притворись, что у него есть этот атрибут x для члена y», я бы предпочел не изобретать.

Ответ №1:

Провел небольшое исследование, и, похоже, вы можете использовать custom JsonContractResolver во время десериализации, что обеспечит сериализатор правильным создателем объекта.
Итак, десериализация выглядит так:

 var settings = new JsonSerializerSettings { ContractResolver = new MyContractResolver() };
var myClass = JsonConvert.DeserializeObject<MyClass>("{ "Foo": "Hello", "Bar": true }", settings);
  

И MyContractResolver является:

 public class MyContractResolver : DefaultContractResolver
{
    public override JsonContract ResolveContract(Type type)
    {
        var contract = base.ResolveContract(type);
        if (type == typeof(MyClass) amp;amp; contract is JsonObjectContract objectContract)
        {
            objectContract.OverrideCreator = (args) => new MyClass((string)args[0], (bool)args[1]);
            var overrideConstructor = typeof(MyClass).GetConstructor(new[] { typeof(string), typeof(bool) });
            foreach (var param in CreateConstructorParameters(overrideConstructor, objectContract.Properties))
                objectContract.CreatorParameters.Add(param);
        }
        return contract;
    }
}
  

Конечно, это очень схематично, и JsonSerializer атрибут гораздо удобнее использовать. Однако, если у вас есть устаревшие классы, это может помочь.

Идея заключается в том, что при Serializer заполнении JsonContract для вашего типа — позвольте определителю контрактов по умолчанию создать его, а затем внедрить конструктор, который вам действительно нужно вызвать в OverrideCreator свойстве contract, и описание его аргументов CreatorParameters .

Комментарии:

1. Кажется, подход ставки. Огляделся в этом коде, есть переопределяемый CreateObjectContract . На самом деле он используется GetAttributeConstructor для поиска атрибута, но, похоже, он не переопределяется, похоже DefaultContractResolver , обрабатывает кучу атрибутов.