getting "fatal error: NSArray element failed to match the Swift Array Element type" when trying to read values from an array of class objects

12,389

Solution 1

OK, This was a beginner problem that might help others.

The scenario: I have a complex JSON: root is object, for each key in the object there is a numeric value (deferent key but the value has the same structure) and the value is the Category Object! For each category there are many key/Value pairs and one of them is the the Books KEY and the value is array or objects, each one is a book.

The problem: when i deserialised the JSON, i parsed the categories with an array of books, but the books as mentioned in the comments weren't really book instances more array of NSDictionary objects.

So after deserialising the Category i have to add an init method in The Book class to get an NSDictionary and parse it into a Book Object.

After that for each category i have to go over the the NSArray of books and for each one to create an Object go Book and return an Array of Books.

That did the trick and now every thing is working:

API:

// Getting NSArray of NSDictionary that represnets books and convert it into Books array
static func getBooksFromArr(booksArr: NSArray) -> [Book] {
    var books = [Book]()

    for book in booksArr {
        let thisBook = Book(bookDict: book as! NSDictionary)
        books.append(thisBook)
        print(book)
    }

    return books
}

// Parsing the category object
private static func CategoryFromJSONObject(json: [String: AnyObject]) -> Category? {
    guard let
        id = json["Id"] as? Int,
        name = json["Name"] as? String,
        booksArr = json["Books"] as? NSArray else {
            return nil
    }

    let books = getBooksFromArr(booksArr)

    return Category(name: name, id: id, books: books)
}

Book Class:

init(bookDict: NSDictionary){
    self.name = (bookDict["Name"] ?? "") as! String
    self.id = (bookDict["Id"] ?? -1) as! Int
    self.categoryId = (bookDict["CategoryId"] ?? -1) as! Int
    self.bookAuthor = (bookDict["BookAuthor"] ?? "") as! String
    self.level1Name = (bookDict["Level1Name"] ?? "") as! String
    self.level2Name = (bookDict["Level2Name"] ?? "") as! String
    self.level3Name = (bookDict["Level3Name"] ?? "") as! String
    self.level4Name = (bookDict["Level4Name"] ?? "") as! String
    self.levels = (bookDict["Levels"] ?? -1) as! Int
    self.orderKey = (bookDict["OrderKey"] ?? -1) as! Int
    self.viewCount = (bookDict["ViewCount"] ?? -1) as! Int
    self.viewLevel = (bookDict["ViewLevel"] ?? -1) as! Int
    self.chapters = (bookDict["Chapters"] ?? []) as! [AnyObject]
}

Now i will have to do the same for the chapters if i want to compose books to hold the chapters.

I didn't write the REST API so don't ask me why they did it as so, but now every thing is working and that is the point of it.

Solution 2

Ok, let's start with your error.

fatal error: NSArray element failed to match the Swift Array Element type

A swift array is complaining that one of it's elements is not what it's expecting. The NSArray element does not match the swift array element.

Now we know that a NSArray can store different types of elements, for example, NSNumber, NSDictionary, NSString... you get the picture. So clearly our issue here is type related.

Now looking at your segue code we can see that we do not actually state what type books is, we let swift's type inference work it out for us. This is where our first sign of the issue occurs. You are expecting an array of [Book] but for some reason you are getting a type of NSArray.

If you make the change from:

let books = categoryStore.allCategories[selectedIndexPath.row].books
let books : [Book] = categoryStore.allCategories[selectedIndexPath.row].books

You will now crash at this line, because you are now stating the type expected.

Full Method:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "ShowBook" {
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            let books : [Book] = categoryStore.allCategories[selectedIndexPath.row].books

            let destinationVC = segue.destinationViewController as! BookViewController
            destinationVC.books = books
        }
    }
}

This is backed up by your debugger console output, it says that you have an array of 13 NSDictionary types.

This means you need to investigate where you populate the [Book] array - i.e. categoryStore.allCategories

Share:
12,389
Erez
Author by

Erez

Updated on June 19, 2022

Comments

  • Erez
    Erez almost 2 years

    There is an issue that i can't understand and can not find an answer to: i have this method in a tableViewCOntroller that is calling another viewCOntroller with TableView inside it

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "ShowBook" {
            if let selectedIndexPath = tableView.indexPathForSelectedRow {
                let books = categoryStore.allCategories[selectedIndexPath.row].books
    
                let destinationVC = segue.destinationViewController as! BookViewController
                destinationVC.books = books
            }
        }
    }
    

    Then in the BookViewController i have this just for testing:

    var books = [Book]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        print(books[0].name)
    }
    

    I can see that the books var is an array that is holding books:

    class Book {
    var name: String = ""
    var id: Int = -1
    var categoryId: Int = -1
    var bookAuthor: String = ""
    var level1Name: String = ""
    var level2Name: String = ""
    var level3Name: String = ""
    var level4Name: String = ""
    var levels: Int = -1
    var orderKey: Int = -1
    var viewCount: Int = -1
    var viewLevel: Int = -1
    var chapters: [AnyObject] = []
    

    }

    so i am getting an array of books with 13 key/value pairs dictionary in the books var

    when i'm trying to print any thing, lets say:

    print(books[0].name)
    

    I get the error:

    fatal error: NSArray element failed to match the Swift Array Element type

    and i can't understand why...

    p.s The transition is working and i can see the next table but then getting the fatal error

    enter image description here