-1

I'm stuck. I have json (array of Movies). I'm trying parse it with Codable protocol, and save to Core Data. Problem is that Movie object have array of Genres (array of strings). I created two entities: Movie and Genre (with relation One to Many). Parsing Movie object not have problem, but when I try to parse genres - its not working.

Have any idea?

P.S. Yes I know that genre array not have key "name".

{
"title": "Dawn of the Planet of the Apes",
"image": "https://api.androidhive.info/json/movies/1.jpg",
"rating": 8.3,
"releaseYear": 2014,
"genre": ["Action", "Drama", "Sci-Fi"]
},
{
"title": "District 9",
"image": "https://api.androidhive.info/json/movies/2.jpg",
"rating": 8,
"releaseYear": 2009,
"genre": ["Action", "Sci-Fi", "Thriller"]
}

Movie model:

@objc(Movie)
class Movie: NSManagedObject, Decodable {

    @NSManaged var title: String?
    @NSManaged var image: String?
    @NSManaged var rating: Float
    @NSManaged var releaseYear: Int
    @NSManaged var genres: Set<Genre>?

    enum apiKey: String, CodingKey {
        case title
        case image
        case rating
        case releaseYear
        case genres = "genre"
    }

    @nonobjc public class func request() -> NSFetchRequest<Movie> {
        return NSFetchRequest<Movie>(entityName: "Movie")
    }

    // MARK: - Decodable

    public required convenience init(from decoder: Decoder) throws {

        guard let contextUserInfoKey = CodingUserInfoKey.context,
            let manageObjContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let manageObjMovie = NSEntityDescription.entity(forEntityName: "Movie", in: manageObjContext) else {
            fatalError("Error to getting context")
        }

        self.init(entity: manageObjMovie, insertInto: manageObjContext)

        let container = try decoder.container(keyedBy: apiKey.self)
        self.title = try container.decodeIfPresent(String.self, forKey: .title)
        self.image = try container.decodeIfPresent(String.self, forKey: .image)
        self.rating = try container.decodeIfPresent(Float.self, forKey: .rating) ?? 0
        self.releaseYear = try container.decodeIfPresent(Int.self, forKey: .releaseYear) ?? 0

        self.genres = try container.decodeIfPresent(Set<Genre>.self, forKey: .genres) ?? []        
    }
}

// MARK: Generated accessors for geonames
extension Movie {

    @objc(addGenresObject:)
    @NSManaged func addToGenres(_ value: Genre)

    @objc(setKeyObject:)
    @NSManaged func setKeyObject(_ value: String)
}

Genre model:

@objc(Genre)
class Genre: NSManagedObject, Decodable {

    @NSManaged var name: String?

    enum apiKey: String, CodingKey {
        case name
    }

    @nonobjc public class func request() -> NSFetchRequest<Genre> {
        return NSFetchRequest<Genre>(entityName: "Genre")
    }

    // MARK: - Decodable

    public required convenience init(from decoder: Decoder) throws {

        guard let contextUserInfoKey = CodingUserInfoKey.context,
            let manageObjContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let manageObjGenre = NSEntityDescription.entity(forEntityName: "Genre", in: manageObjContext) else {
            fatalError("Error to getting context")
        }

        self.init(entity: manageObjGenre, insertInto: manageObjContext)

        let container = try decoder.container(keyedBy: apiKey.self)
        self.name = try container.decodeIfPresent(String.self, forKey: .name)
    }
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
Sergey Krasiuk
  • 198
  • 2
  • 9

1 Answers1

2

You need an inverse relationship to Movie in Genre. Add this

@NSManaged var movie: Movie?

and establish the connection in the model file.

Then decode an array of strings, map it to Genre instances and assign self to that relationship at the end of the init method

let genreData = try container.decodeIfPresent([String].self, forKey: .genres) ?? []
let genreArray = genreData.map { name in
    let genre = Genre(context: manageObjContext)
    genre.name = name
    genre.movie = self
    return genre
}
self.genres = Set(genreArray)

Consider to use a to-many relationship from Genre to Movie as well because otherwise you will have a lot of Genre instances with the same name. And consider also to reduce the optionals in the Core Data classes. It seems that the JSON source provides always all fields. You can get rid of a lot of redundant code.

vadian
  • 274,689
  • 30
  • 353
  • 361