skip to Main Content

I am new to swift . I am trying to convert json into model by using swift . I am using generic functions to complete the functions . Here is the structure of the json .

screenshot of the json structure

Here is the model I created based on jason .

 import Foundation

// MARK: - Welcome
struct Welcome: Codable {
    let photos: [Photo]
}

// MARK: - Photo
struct Photo: Codable {
    let id, sol: Int
    let camera: Camera
    let imgSrc: String
    let earthDate: String
    let rover: Rover

    enum CodingKeys: String, CodingKey {
        case id, sol, camera
        case imgSrc = "img_src"
        case earthDate = "earth_date"
        case rover
    }
}

// MARK: - Camera
struct Camera: Codable {
    let id: Int
    let name: String
    let roverID: Int
    let fullName: String

    enum CodingKeys: String, CodingKey {
        case id, name
        case roverID = "rover_id"
        case fullName = "full_name"
    }
}

// MARK: - Rover
struct Rover: Codable {
    let id: Int
    let name, landingDate, launchDate, status: String

    enum CodingKeys: String, CodingKey {
        case id, name
        case landingDate = "landing_date"
        case launchDate = "launch_date"
        case status
    }
}

Here is the code in generic function.

func getModel<Model: Codable>(_ type: Model.Type, from url: String, completion: @escaping (Result<Model, NetworkError>) -> ()) {
        
        guard let url = URL(string: url) else {
            completion(.failure(.badURL))
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                completion(.failure(.other(error)))
                return
            }
            
            if let data = data {
                do {
                    let response = try JSONDecoder().decode(type, from: data)
                    completion(.success(response))
                } catch let error {
                    completion(.failure(.other(error)))
                }
            }
        }
        .resume()
        
    }

I am trying to call this function form controller but it is showing the error Value of type ‘Post’ has no member ‘data’

Here is the code to call the function.

class ViewModel {
    
  private   let networkManager  = NetworkManager()
    
    private var rovers  = [Post]()
    
    func getStories (){
        networkManager
            .getModel(Post.self, from: NetworkURLs.baseURL) {[weak self]result in
                
                switch result{
        
                case .success(let response):
                    self?.rovers = response.data.camera.map{$0.data} **// error on this line** 
                case .failure( let error):
                    print( error.localizedDescription)
                }
            }
        
    }

2

Answers


  1. Your response is of type Post which has no property data. You’ll need to extract your photos array from the response, and then map across that array and retrieve the rovers property from it.

    I think what you meant to write was

    self?.rovers = response.photos.camera.map{$0.rover}
    

    However even that won’t work as your data structures don’t match your JSON. From what can be seen, rover is a property on photo not on camera.

    You will need to validate the JSON -> Model mapping

    EDIT after JSON linked in comment below:

    Using the JSON from the API, it confirms that camera and rover sit at the same level in the JSON:

    {
      "photos": [
        {
          "id": 102693,
          "sol": 1000,
          "camera": {
            "id": 20,
            "name": "FHAZ",
            "rover_id": 5,
            "full_name": "Front Hazard Avoidance Camera"
          },
          "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FLB_486265257EDR_F0481570FHAZ00323M_.JPG",
          "earth_date": "2015-05-30",
          "rover": {
            "id": 5,
            "name": "Curiosity",
            "landing_date": "2012-08-06",
            "launch_date": "2011-11-26",
            "status": "active"
          }
        },
        ....
    

    So you will need to change your data model:

    struct Photo : Codable{
        let id : Int
        let sol : Int
        let camera : Camera
        let imgSrc: String
        let earthDate: String
        let rover: Rover
    }
    

    and then to decode it

    self?.rovers = response.photos.map{$0.rover}
    

    nb. in Swift all struct types should be capitalised by convention.

    Login or Signup to reply.
  2. Your struct of type Post does not have a member called "data", indeed.
    You seem to be assuming, that your response object is of type Photo – but the error message is telling you, that it is of type Post, which only holds an array of Photo objects.

    Try something like:

    response.photos[0] to get the first Photo object out of the array – if there is one.
    Then, assuming you got one response.photos[0].data gives you a Camera object already – you seem to be calling via the type, instead of the member name.
    So in case you want to go one step further and access a Rover object, you need to do: response.photos[0].data.data

    I see, that you want to extract several Rovers, supposedly one from each Post, but this will clash with your initial rovers variable being assigned a type of an array of Posts – this means you have to change it to [Rover]. I’m not sure if the map-function is actually suitable for what you want to do here.
    Using a loop, iterating through the Posts and appending Rover objects to the Rover array would be the "manual" way to do it.

    Hope this helps.

    Edit: because you have edited your model mid-question, I can’t see where "Post" has gone now. My reply might only fit the way the original question was posted.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search