[NSJSONSerialization dataWithJSONObject:options:error:]: недопустимый тип верхнего уровня при записи JSON

Когда я запускаю код, я получаю ошибку ---------------------- "[NSJSONSerialization dataWithJSONObject:options:error:]: Неверный тип верхнего уровня в JSON писать". ----------------------

class BaseAPI<T: TargetType> {
    
    func fetchData<M: Decodable>(target: T, responseClass: M.Type, completion:@escaping(Result<M?, NSError>) -> Void) {
        let method = Alamofire.HTTPMethod(rawValue: target.methods.rawValue)
        let headers = Alamofire.HTTPHeaders(target.headers ?? [:])
        let params = buildParams(task: target.task)
        AF.request(target.baseUrl + target.path, method: method, parameters: params.0, encoding: params.1, headers: headers).responseDecodable(of: M.self) { response in
            guard let statusCode = response.response?.statusCode else {
                //ADD Custom Error
                completion(.failure(NSError()))
                return
            }
            if statusCode == 200 {
                // Successful Request
                guard let jsonResponse = try? response.result.get() else {
                    completion(.failure(NSError()))
                    return
                }
                guard let theJSONData = try? JSONSerialization.data(withJSONObject: jsonResponse, options: []) else {
                    completion(.failure(NSError()))
                    return
                }
                guard let responseObj = try? JSONDecoder().decode(M.self, from: theJSONData) else {
                    completion(.failure(NSError()))
                    return
                }
                completion(.success(responseObj))
                
            } else {
                completion(.failure(NSError()))
            }
        }
        
        
    }
    
    private func buildParams(task: Task) -> ([String:Any], ParameterEncoding) {
        switch task {
            
        case .requestPlain:
            return ([:], URLEncoding.default)
        case .requestParameters(parameters: let parameters, encoding: let encoding):
            return (parameters, encoding)
        }
    }
}

вместо try? JSONSerialization.data(withJSONObject: jsonResponse, options: []) Когда я пробую код try? JSONSerialization.data(withJSONObject: jsonResponse, options: []), он выдает ошибку No exact matches in call to class method 'jsonObject'.

Это типа M.

Rob 28.04.2024 20:30

Саналкрлк, пожалуйста, простите за замечание по совершенно несвязанной теме, но я мог бы предложить переименовать ваш тип Task, чтобы избежать коллизий с объектом Swift concurrency Task. Позже, когда вы начнете рассматривать параллелизм в Swift, это только станет источником путаницы.

Rob 29.04.2024 19:09
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

jsonResponse уже является декодированным объектом M. Нельзя вызвать JSONSerialization метод data(withJSONObject:options:) с произвольным объектом. Он работает только с очень специфическими типами, а именно со словарями со строками, числами и т. д. (Список допустимых типов см. в JSONSerialization документации.) Вот почему вы получаете эту ошибку.


При этом возникает вопрос, зачем вообще пытаться перекодировать объект обратно в Data с помощью JSONSerialization и перекодировать этот Data с помощью JSONDecoder обратно в M. Я бы предложил устранить это, упростив код:

func fetchData<M: Decodable>(target: T, responseClass: M.Type = M.self, completion: @escaping(Result<M, Error>) -> Void) {
    let method = Alamofire.HTTPMethod(rawValue: target.methods.rawValue)
    let headers = Alamofire.HTTPHeaders(target.headers ?? [:])
    let params = buildParams(task: target.task)
    AF.request(target.baseUrl + target.path, method: method, parameters: params.0, encoding: params.1, headers: headers)
        .validate()
        .responseDecodable(of: M.self) { response in
            switch response.result {
            case .success(let value): completion(.success(value))
            case .failure(let error): completion(.failure(error))
            }
        }
}

Несколько наблюдений:

  1. В качестве небольшого уточнения я бы посоветовал сделать responseClass необязательным параметром (поскольку тип часто можно определить из контекста). Но я сохранил параметр для тех случаев, когда компилятор не может автоматически определить тип.

  2. Я также использовал validate, чтобы проверить код статуса. Вам не нужно вручную проверять это самостоятельно.

  3. Также обратите внимание: в процессе упрощения я отказался от использования try?. try? является антипаттерном, поскольку он отбрасывает значимую диагностическую информацию.

    Но мы всегда передаем значимую ошибку: если вызывающую программу не интересуют детали, она может игнорировать конкретную возникшую ошибку. Но в тех случаях, когда у вас действительно есть ошибки синтаксического анализа, чрезвычайно полезно иметь подробную информацию (чтобы вы знали, какие поля вызвали проблему).

  4. Я также изменил тип возвращаемого значения на Result<M, Error>. Две вещи, на которые следует обратить внимание:

    • «Успех» не обязательно должен быть необязательным M?, скорее это может быть M. Если все прошло успешно, нет необходимости требовать от вызывающей стороны развернуть необязательный элемент, которого никогда не будет nil.
    • «Неудача» — это Error, а не NSError. Теоретически это может быть AFError (хотя мне обычно не нравится распространять вызывающие типы, специфичные для Alamofire).

Кстати, параллельное исполнение Swift может выглядеть так:

func fetchData<M: Decodable>(target: T, responseClass: M.Type = M.self) async throws -> M {
    let params = buildParams(task: target.task)

    return try await AF.request(
        target.baseUrl + target.path,
        method: Alamofire.HTTPMethod(rawValue: target.methods.rawValue),
        parameters: params.0,
        encoding: params.1,
        headers: target.headers.flatMap { Alamofire.HTTPHeaders($0) }
    )
    .validate()
    .serializingDecodable(M.self)
    .value
}

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