carson-katri / swift-request

Declarative HTTP networking, designed for SwiftUI
MIT License
727 stars 41 forks source link

How to parse array inside array? #19

Closed sabina1997 closed 4 years ago

sabina1997 commented 4 years ago

I have a json like this and I want to make a RequestView loading all categories { "error": false, "message": "Success", "data": [ { "id": 5, "name": "Man", "image": "phpSLlQP3.png", "has_children": "yes" }, { "id": 6, "name": "Woman", "image": "php1g8L0Q.png", "has_children": "no" } }

carson-katri commented 4 years ago

That JSON looks fairly simple to represent with a Decodable struct.

struct APIRes: Decodable {
    let error: Bool
    let message: String
    let data: [Person]
    struct Person: Decodable {
        let id: Int
        let name: String
        let image: String
        let has_children: String
    }
}

Then you can use this initializer to create a RequestView that decodes to that type: public init<ResponseType: Decodable>(_ type: ResponseType.Type, _ request: Request, @ViewBuilder content: @escaping (ResponseType?) -> TupleView<(Content, Placeholder)>) {

Let me know if that works for you or if you need any more help.

sabina1997 commented 4 years ago

Sorry I am new to swift and i am starting with SwiftUi . Can you show me an example in view like var body: some View { NavigationView { RequestView(Decodable, Request { Url(RestManager.baseUrl()) Header.Accept(.json) Header.ContentType(.json) Header.Authorization(.bearer(RestManager.headers())) }) { data in List(data != nil ? try! JSONDecoder().decode([APIRes].self, from: data!) : []) { category in

// need to get all categories here, in your example Person struct

}
Text("Loading...")
} .navigationBarTitle(Text("Categories")) } }

carson-katri commented 4 years ago

Try this. I commented it with some explanations, hopefully those help:

struct APIRes: Decodable {
    let error: Bool
    let message: String
    let data: [Category]
    struct Category: Decodable {
        let id: Int
        let name: String
        let image: String
        let has_children: String
    }
}
struct MyView: View {
    var body: some View {
        NavigationView {
            // Here we give it the type that we expect back.
            // To get the actual type (not an instance) in Swift we add `.self` to the end
            // This tells the RequestView to try and map the JSON we get to the `APIRes` struct
            RequestView(APIRes.self, Request {
                Url(RestManager.baseUrl())
                Header.Accept(.json)
                Header.ContentType(.json)
                Header.Authorization(.bearer(RestManager.headers()))
            }) { (data: APIRes?) in // here we get the data back. It's optional, but already in the `APIRes` format.
                // We check if it's null, then force unwrap it if it's not, and grab the category array from the `data` property.
                List(data != nil ? data!.data : []) { (category: APIRes.Category) in
                    Text(category.name)
                }
                Text("Loading...")
            }
            .navigationBarTitle(Text("Categories"))
        }
    }
}
carson-katri commented 4 years ago

When you use that initializer, swift-request handles decoding the data for you.

sabina1997 commented 4 years ago
Screenshot 2020-04-11 at 6 58 43 PM

I tried this but i got this error

carson-katri commented 4 years ago

You can pass an ID to the list like so:

List(data != nil ? data!.data : [], id: \.id) { (category: APIRes.Category) in
...
}
sabina1997 commented 4 years ago

now its working, thank you very much for your help :)

carson-katri commented 4 years ago

SwiftUI requires all list items to conform to the Identifiable protocol so that when you try to do things like animations, it remembers which array item belongs to which UI element, among other things. The other way to give an ID is to pass the ID as a KeyPath. Here's some resources on this stuff if you want to learn more: