Использование дженериков в C# для определения подсвойства для десериализации JSON завершается неудачей из-за странности конструктора

Я собираюсь реализовать тестовый проект на api-football.com. Их ответы всегда имеют одну и ту же форму — пример можно увидеть здесь. Свойства «Получить», «Параметры», «Ошибки», «Результаты» и «Ответ» всегда находятся в формате JSON, но форма «Ответа» зависит от того, что вы вызываете. Остальные свойства остаются прежними.

Мне было интересно, как смоделировать это на C#, и мне пришла в голову идея использовать Generics для представления свойства Response — таким образом я мог бы полуэлегантным образом обрабатывать множество различных типов ответов.

Итак, я создал такой базовый класс:

public abstract class ApiFootballResponseBase<TResponse>
    where TResponse : IApiFootballResponseType
{
    public string Get { get; set; }

    [JsonConverter(typeof(ApiFootballDictOrEmptyArrayJsonConverter))]
    public Dictionary<string, string> Parameters { get; set; }

    [JsonConverter(typeof(ApiFootballDictOrEmptyArrayJsonConverter))]
    public Dictionary<string, string> Errors { get; set; }

    public ApiFootballPaging Paging { get; set; }

    public int Results { get; set; }

    public TResponse Response { get; set; }

    [JsonConstructor]
    public ApiFootballResponseBase()
    {
    }
}

IApiFootballResponseType — это просто пустой интерфейс маркера. JsonConverter, используемый с параметрами и ошибками, был написан мной для обработки того факта, что эти поля, когда они пусты, просто устанавливаются в пустой массив ([]), а если они что-то содержат, они представляют собой пакеты свойств, которые я хочу вернуть в виде словарей. JsonConverter был протестирован и работает достаточно хорошо.

Затем у нас есть пример класса, который я хочу использовать для получения информации о состоянии API по указанному выше URL-адресу.

public class ApiFootballStatusResponse : IApiFootballResponseType
{

    public ApiFootballSubscriptionInformation Subscription { get; set; }
    // TODO: Requests

    public ApiFootballAccountInformation Account { get; set; }

    [JsonConstructor]
    public ApiFootballStatusResponse()
    {
        
    }
}

Эти типы свойств также представляют собой простые пакеты свойств с аннотациями [JsonConstructor] в конструкторе без параметров.

И причиной этого является исключение, которое я получаю при попытке десериализации с помощью RestSharp и System.Text.Json:

NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'ApiFootball.Responses.ApiFootballResponseBase`1[ApiFootball.Responses.ApiFootballStatusResponse]'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.

Однако, как вы можете видеть, я попытался добавить конструкторы без параметров ко всем классам и пометил их как JsonConstructor (тот, который принадлежит System.Text.Json, а не NewtonSoft).

Действительно ли это исключение пытается сказать мне что-то еще? Это плохой способ попытаться обработать свойство Response с разным содержимым в зависимости от запрошенного ресурса? Если да, то какой способ не воссоздавать общие свойства всех запросов в разных классах/записях?

Обновлено: В ответ на запрос дополнительного кода из dbc в комментариях, вот (недавно добавленный) тестовый код, в котором я пытаюсь десериализовать его самостоятельно, вместо того, чтобы оставлять его RestSharp:

    public async Task<ApiFootballResponseBase<ApiFootballStatusResponse>> GetStatus()
    {
        var request = new RestRequest("status", Method.Get);
        var result = await _client.GetAsync(request);
        
        var json = result.Content ?? "";

        return JsonSerializer.Deserialize<ApiFootballResponseBase<ApiFootballStatusResponse>>(json);
    }

Я написал это так, чтобы было проще использовать отладчик. Это дает мне то же исключение, что и выше, когда он запускает оператор возврата - NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'ApiFootball.Responses.ApiFootballResponseBase1[ApiFootball.Responses.ApiFootballStatusResponse]'`

Если я смогу создать дополнительный код, который поможет понять, почему это не удается, дайте мне знать.

Этот вопрос похож на: Обработка одного объекта и массива JSON. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.

gilliduck 05.08.2024 22:28

Это не точная копия stackoverflow.com/questions/44100383/…, но я думаю, что конечный результат достаточно перекликается, чтобы соответствовать тому, что вы ищете.

gilliduck 05.08.2024 22:28

@gilliduck Основное различие между моим вопросом и их, вероятно, заключается в том, что вопрос, на который вы ссылаетесь, касается Newtonsoft.json, а мой — System.Text.Json. Таким образом, хотя эти два понятия зачастую удивительно похожи, в данном случае это МОЖЕТ быть не так. :)

Rune Jacobsen 06.08.2024 06:17

@RuneJacobsen - не могли бы вы отредактировать свой вопрос, чтобы поделиться минимально воспроизводимым примером , который демонстрирует NotSupportedException при использовании System.Text.Json? Сообщение Deserialization of types without a parameterless constructor может вводить в заблуждение, например, я считаю, что STJ сгенерирует это сообщение, когда вы попытаетесь десериализовать абстрактный тип. Итак, нам нужно увидеть минимально воспроизводимый пример, показывающий, как это происходит. Спасибо!

dbc 07.08.2024 22:14

@dbc Я добавлю больше кода, как только доберусь до того места, где находится мой код, через пару часов. В настоящее время я делаю это с помощью RestSharp, но перенесу десериализацию в свой собственный код и вставлю его сюда, чтобы показать, как это делается с объектами выше. Что касается предоставленных вами ссылок, я также получил собственный JsonConverter, аналогичный вашему принятому ответу, за исключением того, что он возвращает Dictionary<string,string>, если там был какой-либо контент. :)

Rune Jacobsen 08.08.2024 08:34

@dbc Я добавил простую функцию для десериализации результата самостоятельно, вместо того, чтобы оставлять ее RestSharp, и добавил ее выше. То же исключение при запуске в отладчике.

Rune Jacobsen 09.08.2024 06:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
106
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Действительно ли это исключение пытается сказать мне что-то еще?

Да. ApiFootballResponseBase<> — это класс abstract, то есть его необходимо расширить каким-либо другим классом, чтобы можно было создать экземпляр. Вот очень минимальное воспроизведение, которое создает то же сообщение об ошибке, которое вы видите:

JsonSerializer.Deserialize<Foo>("{}").Dump();

public abstract class Foo
{
}

«Публичные» конструкторы в абстрактных классах фактически аналогичны защищенным конструкторам . По моему мнению, компилятор вообще не должен был позволять вам объявлять конструктор public для абстрактного класса. Хотя существует правило анализатора кода, которое может это обнаружить, по умолчанию оно не включено. В любом случае, System.Text.Json должно быть более полезным при вызове, когда тип класса, который вы создаете, является абстрактным, а не при фокусировке на отсутствии общедоступного конструктора.

Я предполагаю, что ключевое слово abstract осталось от некоторых других вещей, которые вы пробовали, связанных с расширением базового класса. Попробуйте удалить ключевое слово abstract (и, возможно, удалите слово «Base» из названия класса, пока вы это делаете).

Аннотация исходит из попытки гарантировать, что базовый класс не может быть создан сам по себе, что, я думаю, не будет проблемой, поскольку он все равно имеет этот параметр типа. Этот код, по сути, представляет собой попытку выяснить, как с этим справиться. Я не знал о ситуации с конструкторами абстрактного класса — спасибо, теперь все работает как положено!

Rune Jacobsen 09.08.2024 20:00

Другие вопросы по теме