Search code examples
jsonswiftdecodable

How to set a variable to multiple types in data model


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.


Solution

  • 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).