skip to Main Content

My app gets its data from an API endpoint.

The data is a Review and the model looks like this:

struct Review: Identifiable, Decodable {
    var id: Int
    var reviewable_type: String
    var score: Int?
    var reviewable: <THIS CAN BE A MOVIE, A SHOW OR AN EPISODE>
    var user: User
    var created_at: String?
    var created: String?
    var text: String?
}

As you can see the reviewable property can be of multiple types.
How do I implement this? I have different data models for Movie, Show and Episode.
Note: the reviewable_type holds the value of the type that’s in the reviewable variable. I don’t know if that helps.

I tried things with protocols and extensions, but can’t seem to get it to work.

2

Answers


  1. Chosen as BEST ANSWER

    I like AntonioWar's answer if the objects all have the same fields, which they don't. Maybe it still works that way but it wasn't clear to me from his answer.

    What I did to solve it was this: I created a Reviewable struct with all the fields from the Show, Episode and Movie structs:

    struct Reviewable : Decodable {
        var id: Int
        var tmdb_id: Int?
        var name: String?
        var show_id: Int?
        var season: String?
        var number: String?
        var type: String?
        var airdate: String?
        var airtime: String?
        var airstamp: String?
        var average_rating: Float?
        var vote_average: String?
        var vote_count: String?
        var image_medium: String?
        var image_original: String?
        var summary: String?
        var watched_by_users: [User]?
        var show: Show?
        var comments_count: Int?
        var likes_count: Int?
        var episode_number: String?
        var new_airstamp: String?
        var backdrop_image: String?
        var poster_image: String?
        var likes: [Like]?
        var reviews: [Review]?
        var imdb_id: String?
        var adult: Int?
        var backdrop_path: String?
        var budget: String?
        var homepage: String?
        var original_language: String?
        var original_title: String?
        var tagline: String?
        var title: String?
        var overview: String?
        var popularity: String?
        var poster_path: String?
        var release_date: String?
        var trending: String?
        var revenue: String?
        var status: String?
        var movie_users: [MovieUser]?
        var tv_maze_id: Int?
        var tvrage_id: String?
        var thetvdb_id: String?
        var original_name: String?
        var language: String?
        var origin_country: String?
        var average_runtime: Int?
        var premiered: String?
        var schedule: String?
        var updated: String?
        var episodes: [Episode]?
        var tracked_by: [User]?
    }
    

    Then, I added this as the type in the Review struct:

    struct Review: Identifiable, Decodable {
        var id: Int
        var reviewable_type: String
        var score: Int?
        var reviewable: Reviewable
        var user: User
        var created_at: String?
        var created: String?
        var text: String?
    }
    

    Then, I created extensions for the 3 Reviewable objects, with a function that converts the Reviewable to the Movie, Episode or Show:

    extension Movie {
        static func fromReviewable(reviewable: Reviewable) -> Movie {
            let movie = Movie(
                id: reviewable.id,
                tmdb_id: reviewable.tmdb_id,
                imdb_id: reviewable.imdb_id,
                adult: reviewable.adult,
                backdrop_path: reviewable.backdrop_path,
                budget: reviewable.budget,
                homepage: reviewable.homepage,
                original_language: reviewable.original_language,
                original_title: reviewable.original_title,
                tagline: reviewable.tagline,
                title: reviewable.title,
                overview: reviewable.overview,
                popularity: reviewable.popularity,
                poster_path: reviewable.poster_path,
                release_date: reviewable.release_date,
                trending: reviewable.trending,
                revenue: reviewable.revenue,
                status: reviewable.status,
                vote_average: reviewable.vote_average,
                vote_count: reviewable.vote_count,
                likes: reviewable.likes,
                movie_users: reviewable.movie_users,
                reviews: reviewable.reviews,
                comments_count: reviewable.comments_count,
                likes_count: reviewable.likes_count
            )
            
            return movie
        }
    }
    

    Because I know which type it is from the reviewable_type variable on the Review, I can use this to decode it in to the right struct:

    if(review.reviewable_type == "App\Models\Movie") {
        let movie = Movie.fromReviewable(reviewable: review.reviewable!)
    } else if(review.reviewable_type == "App\Models\Show") {
        let show = Show.fromReviewable(reviewable: review.reviewable!)
    } else if(review.reviewable_type == "App\Models\Episode") {
        let episode = Episode.fromReviewable(reviewable: review.reviewable!)
    }
    

    Maybe it is a very shabby way of doing it but this is what worked for me (and maybe someone else can use it).


  2. What you can do is transform the struct Review into a protocol with an associative type, that is another protocol Reviewable.

    protocol Reviewable: Decodable {}
    

    This protocol must at least conform to the Decodable protocol, but it can also possibly contain attributes in common between all the contents, such as an ID for example.

    The struct Review is transformed like this:

    protocol Review: Identifiable, Decodable {
        associatedtype Content: Reviewable
        
        var id: Int { get }
        var score: Int? { get }
        var reviewable: Content { get }
        var created_at: String? { get }
        var created: String? { get }
        var text: String? { get }
    }
    

    You will then have a struct for each type of reviewable content:

    struct Movie: Reviewable {}
    
    struct Show: Reviewable {}
    
    struct Episode: Reviewable {}
    

    And one struct for each Review Type.

    struct MovieReview: Review {
        var id: Int
        var score: Int?
        var reviewable: Movie
        var created_at: String?
        var created: String?
        var text: String?
    }
    
    struct ShowReview: Review {
        var id: Int
        var score: Int?
        var reviewable: Show
        var created_at: String?
        var created: String?
        var text: String?
    }
    
    struct EpisodeReview: Review {
        var id: Int
        var score: Int?
        var reviewable: Episode
        var created_at: String?
        var created: String?
        var text: String?
    }
    

    They can then all be used within data structures of type any Review. Here’s a simple example:

    let test: [any Review] = [
                                ShowReview(id: 0, reviewable: Show()),
                                MovieReview(id: 1, reviewable: Movie()),
                                EpisodeReview(id: 2, reviewable: Episode())
                             ]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search