Получение категорий в Flutter News Toolkit

Вопрос или проблема

При попытке получить категории с конечной точки категорий, я столкнулся с этой ошибкой: _TypeError (тип 'List<dynamic>' не является подтипом типа 'Map<String, dynamic>' при приведении типа)

Эта ошибка возникает из-за возврата в расширении из myprojectname_api_client.dart:

 Future<CategoriesResponse> getCategories() async {
    final uri = Uri.parse('$_baseUrl/categories');
    final response = await _httpClient.get(
      uri,
      headers: await _getRequestHeaders(),
    );
    final body = response.json();

    if (response.statusCode != HttpStatus.ok) {
      throw BernardinaiApiRequestFailure(
        body: body,
        statusCode: response.statusCode,
      );
    }

    return CategoriesResponse.fromJson(body);
  }

//------------

extension on http.Response {
  Map<String, dynamic> json() {
    try {
      final decodedBody = utf8.decode(bodyBytes);
      log("MyLog $body");
      return jsonDecode(decodedBody) as Map<String, dynamic>;
    } catch (error, stackTrace) {
      Error.throwWithStackTrace(
        BernardinaiApiMalformedResponse(error: error),
        stackTrace,
      );
    }
  }
}

Лог показывает, что decodeBody содержит полный JSON из конечной точки категорий.

Метод getCategories в моем пользовательском классе, который реализует класс NewsDataSource выглядит следующим образом:

Future<List<Category>> getCategories() async {
  final uri = Uri.parse('https://www.mynewsurl.com/wp-json/wp/v2/categories');
  final categoriesResponse = await http.get(uri);

  if (categoriesResponse.statusCode == HttpStatus.ok) {
    // Обработка тела ответа в Map
    final Map<String, dynamic> categoriesData = jsonDecode(categoriesResponse.body) as Map<String, dynamic>;
    
    // Доступ к списку 'categories' из карты
    final List<dynamic> categoriesList = categoriesData['categories'] as List<dynamic>;
    log("MyLog categoriesList: $categoriesList");
    
    // Извлечение имен категорий и возврат их в виде списка перечислений Category
    final List<Category> categories = categoriesList.map((dynamic categoryData) {
      final String categoryName = categoryData as String; // Явное приведение к String
      log("MyLog categoryName: $categoryName");

      return Category.fromString(categoryName); // Использование fromString для получения перечисления
    }).toList();
    
    log("MyLog categories to return: $categories");

    return categories; // Возвращает список имен категорий
  } else {
    // Обработка случаев ошибок с помощью выброса пользовательского исключения
    throw BernardinaiApiRequestFailure(
      statusCode: categoriesResponse.statusCode,
      body: jsonDecode(categoriesResponse.body) as Map<String, dynamic>,
    );
  }
}

///------

class CategoriesInfo {
  const CategoriesInfo(this.slug);

  final String slug;

  CategoriesInfo.fromJson(Map<String, dynamic> json)
      : slug = _isKebabCase(json['slug'] as String)
            ? _convertToCamelCase(json['slug'] as String)
            : json['slug'] as String;

  Map<String, dynamic> toJson() {
      log("mano as veikiu");
    return {
      'slug': slug,
    };
  }

  static bool _isKebabCase(String input) {
    log("MyLog checking what case is used");
    return input.contains('-');
  }

  static String _convertToCamelCase(String input) {
    log("MANO slugas: $input");
    List<String> parts = input.split('-');
    if (parts.length == 1) return input;

    String camelCase = parts[0];
    for (int i = 1; i < parts.length; i++) {
      camelCase += parts[i][0].toUpperCase() + parts[i].substring(1);
    }

    return camelCase;
  }
}

Я пытался изменить тип расширения на список, но тогда все другие методы вызывают ту же ошибку, поэтому теперь я задаюсь вопросом, не должен ли метод Future<List<Category>> getCategories() быть списком, а не картой? Но это было определено так в шаблоне, так что, возможно, это одна из ошибок? Или, может быть, я чего-то не вижу? Я совершенно сбит с толку, и документация не помогает, поэтому любая помощь будет полезна.

Похоже, что проблема заключается в том, что JSON-ответ, который вы получаете с конечной точки /wp-json/wp/v2/categories, на самом деле является списком (массивом в терминах JSON), а не картой (объектом). Это вызывает ошибку приведения типа к Map<String, dynamic>, что приводит к ошибке:

_TypeError (тип 'List<dynamic>' не является подтипом типа 'Map<String, dynamic>' при приведении типа)

Вот как вы можете это исправить:

  1. Обновите свой метод расширения, чтобы обрабатывать динамические JSON-типы:

    Вместо приведения декодированного JSON к Map<String, dynamic>, вы можете позволить ему возвращать динамический тип и обрабатывать как карты, так и списки соответствующим образом.

    extension on http.Response {
      dynamic json() {
        try {
          final decodedBody = utf8.decode(bodyBytes);
          log("MyLog $decodedBody");
          return jsonDecode(decodedBody);
        } catch (error, stackTrace) {
          Error.throwWithStackTrace(
            BernardinaiApiMalformedResponse(error: error),
            stackTrace,
          );
        }
      }
    }
    
  2. Измените свой метод getCategories для обработки списка:

    Измените логику парсинга, чтобы она отражала, что JSON является списком категорий.

    Future<List<Category>> getCategories() async {
      final uri = Uri.parse('https://www.mynewsurl.com/wp-json/wp/v2/categories');
      final categoriesResponse = await http.get(uri);
    
      if (categoriesResponse.statusCode == HttpStatus.ok) {
        // Обработка тела ответа в список
        final List<dynamic> categoriesData = jsonDecode(categoriesResponse.body) as List<dynamic>;
    
        log("MyLog categoriesData: $categoriesData");
    
        // Привязка каждого элемента списка к объекту Category
        final List<Category> categories = categoriesData.map((dynamic categoryData) {
          final Map<String, dynamic> categoryMap = categoryData as Map<String, dynamic>;
          final String categoryName = categoryMap['slug'] as String;
          log("MyLog categoryName: $categoryName");
    
          return Category.fromString(categoryName); // Использование fromString для получения элемента перечисления или создания экземпляра Category
        }).toList();
    
        log("MyLog categories to return: $categories");
    
        return categories; // Возвращает список объектов Category
      } else {
        // Обработка случаев ошибок с помощью выброса пользовательского исключения
        throw BernardinaiApiRequestFailure(
          statusCode: categoriesResponse.statusCode,
          body: jsonDecode(categoriesResponse.body),
        );
      }
    }
    
  3. При необходимости настройте свой класс CategoriesInfo:

    Убедитесь, что ваш конструктор CategoriesInfo.fromJson правильно инициализирует объект из JSON-карты.

    class CategoriesInfo {
      const CategoriesInfo(this.slug);
    
      final String slug;
    
      CategoriesInfo.fromJson(Map<String, dynamic> json)
          : slug = _isKebabCase(json['slug'] as String)
              ? _convertToCamelCase(json['slug'] as String)
              : json['slug'] as String;
    
      // Остальная часть вашего кода...
    }
    

Объяснение:

  • Структура JSON: Конечная точка возвращает JSON-массив (список) объектов категорий, а не JSON-объект (карту). Каждый объект категории в списке содержит данные о категории.

  • Приведение типов: Попытка привести JSON-список к Map<String, dynamic> вызывает ошибку типа, потому что список не является картой. Путем его парсинга как List<dynamic>, вы можете пройтись по каждому элементу.

  • Обработка каждой категории: Каждый элемент в списке categoriesData представляет собой карту, представляющую категорию. Вам нужно привести каждый элемент dynamic к Map<String, dynamic> перед доступом к его полям.

  • Обработка ошибок: Убедитесь, что ваша обработка ошибок не предполагает, что тело ответа является картой. При выбросе BernardinaiApiRequestFailure, вы можете передавать декодированный JSON напрямую без приведения.

Дополнительные советы:

  • Логирование: Используйте логирование, чтобы проверить фактическую структуру JSON-ответа. Это может помочь вам понять, как правильно его парсить.

  • Безопасность типов: Хотя использование dynamic может быть полезным, старайтесь быть как можно более конкретным с вашими типами, чтобы ловить ошибки на этапе компиляции.

  • Документация API: Всегда обращайтесь к документации API или проверяйте необработанный JSON-ответ, чтобы понять его структуру.

Ответ или решение

Получение категорий в Flutter News Toolkit: Решение проблемы с типами данных

Во время работы с API для получения категорий в Flutter News Toolkit вы столкнулись с ошибкой, которая гласит: _TypeError (type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>' in type cast). Эта ошибка возникает из-за несоответствия типов данных, что делаем особенно значимым понимание структуры JSON ответа, получаемого от вашего API. Разберёмся в проблеме и предложим возможные решения.

Подробный анализ проблемы

Ваша функция getCategories() пытается разобрать ответ, предполагая, что он имеет форму JSON-объекта (Map<String, dynamic>), однако, на самом деле API возвращает JSON-массив (список) категорий. Давайте рассмотрим, как можно правильно обработать этот ответ.

Код функции getCategories()

Вот так выглядит ваша текущая версия функции getCategories():

Future<List<Category>> getCategories() async {
  final uri = Uri.parse('https://www.mynewsurl.com/wp-json/wp/v2/categories');
  final categoriesResponse = await http.get(uri);

  if (categoriesResponse.statusCode == HttpStatus.ok) {
    final Map<String, dynamic> categoriesData = jsonDecode(categoriesResponse.body) as Map<String, dynamic>;

    final List<dynamic> categoriesList = categoriesData['categories'] as List<dynamic>;

    final List<Category> categories = categoriesList.map((dynamic categoryData) {
      final String categoryName = categoryData as String;
      return Category.fromString(categoryName);
    }).toList();

    return categories;
  } else {
    throw BernardinaiApiRequestFailure(
      statusCode: categoriesResponse.statusCode,
      body: jsonDecode(categoriesResponse.body) as Map<String, dynamic>,
    );
  }
}

Основные шаги по устранению проблемы

Для решения данной ошибки необходимо внести следующие изменения в вашу функцию:

  1. Измените метод для обработки динамических типов JSON: Не привязывайте результат к фиксированному типу Map<String, dynamic>, а используйте более обобщённый тип dynamic, чтобы корректно обрабатывать как объекты, так и массивы.

  2. Настройте метод getCategories() для обработки списка: Измените логику разбора JSON, чтобы она соответствовала его фактической структуре.

Исправленный код

Ниже приведён обновлённый код, который должен правильно обрабатывать ответ от API:

Future<List<Category>> getCategories() async {
  final uri = Uri.parse('https://www.mynewsurl.com/wp-json/wp/v2/categories');
  final categoriesResponse = await http.get(uri);

  if (categoriesResponse.statusCode == HttpStatus.ok) {
    // Парсим ответ как список
    final List<dynamic> categoriesData = jsonDecode(categoriesResponse.body) as List<dynamic>;

    // Преобразуем каждый элемент списка в объект Category
    final List<Category> categories = categoriesData.map((dynamic categoryData) {
      final Map<String, dynamic> categoryMap = categoryData as Map<String, dynamic>;
      final String categoryName = categoryMap['slug'] as String;

      return Category.fromString(categoryName); // Преобразование в перечисление
    }).toList();

    return categories; // Возвращаем список объектов Category
  } else {
    throw BernardinaiApiRequestFailure(
      statusCode: categoriesResponse.statusCode,
      body: jsonDecode(categoriesResponse.body),
    );
  }
}

Пояснения к изменённому коду

  1. Чтение JSON как списка: Запрос теперь возвращает List<dynamic>, поскольку API возвращает массив категорий. Это позволяет избежать ошибки преобразования.

  2. Преобразование элементов массива: Каждый элемент в списке представляется как Map<String, dynamic>, что позволяет корректно извлекать поля, такие как slug.

  3. Обработка ошибок: Метод теперь не предполагает, что ошибка приведёт к возврату объекта, что позволяет избежать дополнительных исключений.

Завершение

При работе с API крайне важно знать структуру возвращаемого JSON. Используйте логирование для диагностики: log("MyLog categoriesData: $categoriesData"); поможет вам увидеть фактический ответ от сервера. Если это поможет вам лучше понять структуру данных, вы сможете избежать типовых ошибок.

Таким образом, теперь вы можете уверенно работать с категориями из Flutter News Toolkit. Если у вас остались вопросы, не стесняйтесь задавать их!

Оцените материал
Добавить комментарий

Капча загружается...