Как получить JSON API с помощью swiftUI, если полученный ответ находится внутри двух разных массивов?

Цель: показать данные API на панели вкладок с помощью SwiftUI.

Что я пробовал: я попытался реализовать API с помощью модели и реализовал его в виде вкладок.

В чем проблема: я много раз пытался изменить свою модель, но данные все равно не извлекались правильно.

Получение ошибки: данные не могут быть прочитаны, поскольку они имеют неправильный формат.

URL API: https://dev.myhss.org.uk/api/v1/events/eventlist

Необязательный параметр (можно использовать): ключ : тип_события значение: 1(1- предстоящие события, 2- прошедшие события)

тело: x-www-form-unlencoded

Идеальный ответ:

{
    "status": true,
    "message": "Event Listing",
    "data": {
        "upcoming": [
            {
                "event_id": "1",
                "event_title": "event tile",
                "event_start_date": "2024-02-02 16:55:08",
                "event_end_date": "2024-06-20 16:55:08",
                "event_img": "https://dev.myhss.org.uk/assets/events/event-1.png",
                "event_chargable_or_not": "0"
            }
        ],
        "past": [
            {
                "event_id": "2",
                "event_title": "event title 2",
                "event_start_date": "2023-04-15 16:55:08",
                "event_end_date": "2023-05-31 16:55:08",
                "event_img": "https://dev.myhss.org.uk/assets/events/event-2.png",
                "event_chargable_or_not": null
            }
        ]
    }
}

Обновил предложенный код из ответа, но проблема не устранена, Ошибка: данные не могут быть прочитаны из-за неправильного формата.

Код SwiftUI:

import SwiftUI

struct EventListing: Codable {
    var status: Bool
    var message: String
    var data: Event2
    
}
struct Event2: Codable {
    var upcoming: [Event]
    var past: [Event]
    
}

struct Event: Codable, Identifiable {
    var id: String // Add this property to conform to Identifiable
    var title: String
    var startDate: Date
    var endDate: Date
    var imageURL: URL?
    var isChargeable: Bool
    
    enum CodingKeys: String, CodingKey {
        case id = "event_id"
        case title = "event_title"
        case startDate = "event_start_date"
        case endDate = "event_end_date"
        case imageURL = "event_img"
        case isChargeable = "event_chargable_or_not"
    }
}

import SwiftUI

struct OkView: View {
    var body: some View {
        TabView {
            UpcomingEventsView()
                .tabItem {
                    Image(systemName: "calendar.circle")
                    Text("Upcoming")
                }
            PastEventsView()
                .tabItem {
                    Image(systemName: "calendar.circle.fill")
                    Text("Past")
                }
        }
    }
}

struct UpcomingEventsView: View {
    @State private var events: [Event] = []

    var body: some View {
        List(events) { event in
            Text(event.title)
        }
        .onAppear {
            ApiService.fetchEvents(type: .upcoming) { result in
                switch result {
                case .success(let eventListing):
                    self.events = eventListing.data.upcoming
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }
}

struct PastEventsView: View {
    @State private var events: [Event] = []

    var body: some View {
        List(events) { event in
            Text(event.title)
        }
        .onAppear {
            ApiService.fetchEvents(type: .past) { result in
                switch result {
                case .success(let eventListing):
                    self.events = eventListing.data.past
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }
}

enum EventType {
    case upcoming
    case past
}

class ApiService {
    static let baseUrl = "https://dev.myhss.org.uk/api/v1/events/eventlist"
    static let dateFormatter = DateFormatter()
    
    static func fetchEvents(type: EventType, completion: @escaping (Result<EventListing, Error>) -> Void) {
        let endpoint = type == .upcoming ? "events/upcoming" : "events/past"
        guard let url = URL(string: "\(baseUrl)/\(endpoint)") else {
            completion(.failure(ApiError.invalidUrl))
            return
        }
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            guard let data = data else {
                completion(.failure(ApiError.noData))
                return
            }
            do {
                dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .formatted(dateFormatter)
                let eventListing = try decoder.decode(EventListing.self, from: data)
                completion(.success(eventListing))
            } catch let error {
                completion(.failure(error))
            }
        }.resume()
    }
    
    enum ApiError: Error {
        case invalidUrl
        case noData
    }
}

Похоже, есть проблема с вашим php-сервисом. Он возвращает 405 Method Not Allowed

Carsten 19.04.2023 13:33

Вам нужно декодировать ответ json с верхнего уровня, вы не можете начать на один уровень ниже. Поэтому добавьте одну структуру, содержащую ключ data, и используйте этот тип при декодировании. Кроме того, при печати ошибки декодирования используйте print(error), чтобы получить более подробное сообщение, чем при использовании .localizedDescription

Joakim Danielson 19.04.2023 13:34

Связанный: stackoverflow.com/questions/52110124/…

vadian 19.04.2023 13:44

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

Joakim Danielson 19.04.2023 15:36

если вы введете свой URL, https://dev.myhss.org.uk/api/v1/events/eventlist/events/upco‌​ming или с past в свой браузер, вы увидите, что не получаете того, что ожидаете. Вот почему вы получаете ошибку. Поставьте print("--> response: \(String(data: data, encoding: .utf8))") перед do{...} и убедитесь сами.

workingdog support Ukraine 19.04.2023 15:51
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
1
5
119
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы должны изменить свой DTO как

 struct EventListing: Codable {
    var status: Bool
    var message: String
    var data: Event2
    
}
struct Event2: Codable {
    var upcoming: [Event]
    var past: [Event]
    
}

struct Event: Codable, Identifiable {
    var id: String // Add this property to conform to Identifiable
    var title: String
    var startDate: Date
    var endDate: Date
    var imageURL: URL?
    var isChargeable: Bool
    
    enum CodingKeys: String, CodingKey {
        case id = "event_id"
        case title = "event_title"
        case startDate = "event_start_date"
        case endDate = "event_end_date"
        case imageURL = "event_img"
        case isChargeable = "event_chargable_or_not"
    }
} 

в ваших кодах я не вижу часть данных, но это должно быть так. После изменения кода проверьте правильность URL и правильность поступления данных.

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

Попробуйте следующий код, используя URLRequest с заголовком POST вместо вашего URL, и модифицированная модель Event, особенно обратите внимание, var isChargeable: String? не Bool, у меня работает.

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

struct ContentView: View {
    var body: some View {
        OkView()
    }
}

struct OkView: View {
    var body: some View {
        TabView {
            UpcomingEventsView()
                .tabItem {
                    Image(systemName: "calendar.circle")
                    Text("Upcoming")
                }
            PastEventsView()
                .tabItem {
                    Image(systemName: "calendar.circle.fill")
                    Text("Past")
                }
        }
    }
}

struct UpcomingEventsView: View {
    @State private var events: [Event] = []

    var body: some View {
        List(events) { event in
            Text(event.title)
        }
        .onAppear {
            ApiService.fetchEvents(type: .upcoming) { result in
                switch result {
                case .success(let eventListing):
                    self.events = eventListing.data.upcoming
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }
}

struct PastEventsView: View {
    @State private var events: [Event] = []

    var body: some View {
        List(events) { event in
            Text(event.title)
        }
        .onAppear {
            ApiService.fetchEvents(type: .past) { result in
                switch result {
                case .success(let eventListing):
                    self.events = eventListing.data.past
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }
}

enum EventType {
    case upcoming
    case past
}

class ApiService {
    static let baseUrl = "https://dev.myhss.org.uk/api/v1/events/eventlist"
    static let dateFormatter = DateFormatter()
    
    static func fetchEvents(type: EventType, completion: @escaping (Result<EventListing, Error>) -> Void) {

        let eventType = type == .upcoming ? "1" : "0" // <--- here

        // --- here
        guard let url = URL(string: baseUrl) else {
            completion(.failure(ApiError.invalidUrl))
            return
        }
        
        // --- here
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.httpBody = "event_type=\(eventType)".data(using: .utf8)!  // <--- here
 
        // --- here (with: request)
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            guard let data = data else {
                completion(.failure(ApiError.noData))
                return
            }
    //        print("\n----> data: \(String(data: data, encoding: .utf8) as AnyObject) \n")
            do {
                dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .formatted(dateFormatter)
                let eventListing = try decoder.decode(EventListing.self, from: data)
                completion(.success(eventListing))
            } catch let error {
                print("\n----> decoding error: \(error) \n")
                completion(.failure(error))
            }
        }.resume()
    }
    
    enum ApiError: Error {
        case invalidUrl
        case noData
    }
}

struct EventListing: Codable {
    let status: Bool
    let message: String
    let data: DataClass
}

struct DataClass: Codable {
    let upcoming: [Event]
    let past: [Event]
}

struct Event: Codable, Identifiable {
    var id: String // Add this property to conform to Identifiable
    var title: String
    var startDate: Date
    var endDate: Date
    var imageURL: URL?
    var isChargeable: String?  // <--- here
    
    enum CodingKeys: String, CodingKey {
        case id = "event_id"
        case title = "event_title"
        case startDate = "event_start_date"
        case endDate = "event_end_date"
        case imageURL = "event_img"
        case isChargeable = "event_chargable_or_not"
    }
}

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