CoreData Codable Relationships

I have the following data model in CoreData:


The relationships childs and parent are between objects of the same Type (Subject).


Subject+CoreDataClass.swift

import Foundation
import CoreData

@objc(Subject)
public class Subject: NSManagedObject {

}


Subject+CoreDataProperties

import Foundation
import CoreData
extension Subject {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<Subject> {
        return NSFetchRequest<Subject>(entityName: "Subject")
    }
    @NSManaged public var idauto: NSNumber?
    @NSManaged public var longdescription: String?
    @NSManaged public var parentid: NSNumber?
    @NSManaged public var shortdescription: String?
    @NSManaged public var title: String?
    @NSManaged public var childs: NSSet?
    @NSManaged public var parent: Subject?
}


I would like to return a Json file from the content of the sqlite database, so I make the Subject class conform to Codable protocol as follow:


Subject+CoreDataClass.swift

import Foundation
import CoreData
@objc(Subject)
public class Subject: NSManagedObject, Codable {

    enum CodingKeys: String, CodingKey {
        case title
        case shortdescription
        case longdescription
        case parent
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(title ?? "", forKey: .title)
        try container.encode(shortdescription ?? "", forKey: .shortdescription)
        try container.encode(longdescription ?? "", forKey: .longdescription)
        try container.encode(parent, forKey: .parent)
    }


    public required convenience init(from decoder: Decoder) throws {
        guard let contextUserInfoKey = CodingUserInfoKey.context,
            let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let entity = NSEntityDescription.entity(forEntityName: "Subject", in: managedObjectContext) else {  fatalError("Failed to decode Subject!")  }

        self.init(entity: entity, insertInto: nil)

        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decode(String.self, forKey: .title)
        shortdescription = try values.decode(String.self, forKey: .shortdescription)
        longdescription = try values.decode(String.self, forKey: .longdescription)
        parent = try values.decode(Subject?.self, forKey: .parent)
    }
}
extension CodingUserInfoKey {
    static let context = CodingUserInfoKey(rawValue: "context")
}


I did not encode the relationship named childs because I get this error:


Fatal error: Optional<NSSet> does not conform to Encodable because NSSet does not conform to Encodable


To encode the data in an UIViewController I got this code:


import UIKit
import CoreData
class RootViewController: UIViewController {

var managedObjectContext: NSManagedObjectContext!
var subjects : [Subject] = []

    func executeFetchRequest(completion:@escaping () -> ()){
        let fetchRequest: NSFetchRequest<Subject> = Subject.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "TRUEPREDICATE")
        let sortDescriptor = NSSortDescriptor(key: "idauto", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        fetchRequest.returnsObjectsAsFaults = false
        managedObjectContext.perform {
            self.subjects = try! fetchRequest.execute()
            completion()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        executeFetchRequest(){ [weak self] in
            guard let strongSelf = self else { return }
            do{
                let documentDirectoryURL =  try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                let jsonURL = URL(fileURLWithPath: "subjects", relativeTo: documentDirectoryURL.appendingPathComponent("Subjects")).appendingPathExtension("json")
                let jsonEncoder = JSONEncoder()
                jsonEncoder.outputFormatting = .prettyPrinted
                let jsonData = try jsonEncoder.encode(strongSelf.subjects)
                try jsonData.write(to: jsonURL)
            } catch let error as NSError{
                print("Error: \(error.description)")
            }
        }
     }
}



The problem is that I get the following error in the function: public func encode(to encoder: Encoder) throws


Thread 1: EXC_BAD_ACCESS (code=2, address=0x16f4c3ea0)


If I don't encode the relationship paren,t I get the Json file ok.


How could I modify the code to also encode the relationship parent?

Accepted Reply

Finally I found a solution for my issue and would like to share here.


Note that in the class Subject, var id: Int16? and var parentid: Int16? are not @NSManaged and do not exists in .xcdatamodel.


import Foundation
import CoreData
@objc(Subject)
public class Subject: NSManagedObject, Codable {
    public var id: Int16?
    public var parentid: Int16?
    @NSManaged public var longdescription: String?
    @NSManaged public var shortdescription: String?
    @NSManaged public var title: String?
    @NSManaged public var childs: NSSet?
    @NSManaged public var parent: Subject?

    enum CodingKeys: String, CodingKey {
        case id
        case parentid
        case title
        case shortdescription
        case longdescription
    }

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

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id ?? 0, forKey: .id)
        try container.encode(parentid ?? 0, forKey: .parentid)
        try container.encode(title ?? "", forKey: .title)
        try container.encode(shortdescription ?? "", forKey: .shortdescription)
        try container.encode(longdescription ?? "", forKey: .longdescription)
    }

    public required convenience init(from decoder: Decoder) throws {
        guard let contextUserInfoKey = CodingUserInfoKey.context,
            let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let entity = NSEntityDescription.entity(forEntityName: "Subject", in: managedObjectContext) else {  fatalError("Failed to decode Subject!")  }

        self.init(entity: entity, insertInto: managedObjectContext)
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int16?.self, forKey: .id)
        parentid = try values.decode(Int16?.self, forKey: .parentid)
        title = try values.decode(String.self, forKey: .title)
        shortdescription = try values.decode(String.self, forKey: .shortdescription)
        longdescription = try values.decode(String.self, forKey: .longdescription)
    }
}
extension CodingUserInfoKey {
    static let context = CodingUserInfoKey(rawValue: "context")
}



import UIKit
import CoreData
import MessageUI
class RootViewController: UIViewController{

    @IBOutlet weak var tableView: UITableView!

    @IBAction func importData(_ sender: Any) {
        importManager.importData(){ [weak self] in
            guard let strongSelf = self else { return }
            do {
                try strongSelf.fetchedResultsController.performFetch()
                strongSelf.tableView.reloadData()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

    @IBAction func exportData(_ sender: Any) {
        exportManager.exportData()
    }

    private var importManager : ImportManager!
    private var exportManager : ExportManager!

    var managedObjectContext: NSManagedObjectContext!

    var _fetchedResultsController: NSFetchedResultsController<Subject>?
    var fetchedResultsController: NSFetchedResultsController<Subject> {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
     
        let fetchRequest: NSFetchRequest<Subject> = Subject.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "parent == NULL")
        fetchRequest.returnsObjectsAsFaults = false
        fetchRequest.relationshipKeyPathsForPrefetching = ["childs"]
        fetchRequest.fetchBatchSize = 20
        let sortDescriptor = NSSortDescriptor(key: "title", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
        fetchRequest.sortDescriptors = [sortDescriptor]
        let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController
        do {
            try _fetchedResultsController!.performFetch()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
     
        return _fetchedResultsController!
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        importManager = ImportManager(managedObjectContext: managedObjectContext)
        exportManager = ExportManager(managedObjectContext: managedObjectContext)
        exportManager.exportHandler = {
            print("Finish export data")
        }
    }
}
extension RootViewController : UITableViewDelegate{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
        tableView.deselectRow(at: indexPath, animated: false)
    }
}
extension RootViewController : UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return fetchedResultsController.fetchedObjects?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
     
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.reuseIdentifier, for: indexPath) as? TableViewCell else { fatalError("Unexpected cell type at \(indexPath)") }

        let subject = fetchedResultsController.object(at: indexPath)
        cell.configure(for: subject)
     
        return cell
    }
}
extension RootViewController : NSFetchedResultsControllerDelegate {
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        switch type {
        case .insert:
            tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
        case .delete:
            tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
        default:
            return
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
            if let indexPath = indexPath {
                guard let cell = tableView.cellForRow(at: indexPath) as? TableViewCell else { fatalError("Can not get Cell") }
                let subject = fetchedResultsController.object(at: indexPath)
                cell.configure(for: subject)
            }
        case .move:
            tableView.moveRow(at: indexPath!, to: newIndexPath!)
        }
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }
}


import Foundation
import CoreData
class ImportManager{
    var managedObjectContext: NSManagedObjectContext!
  
    init(managedObjectContext:NSManagedObjectContext){
        self.managedObjectContext = managedObjectContext
    }

    func importData(completion:@escaping () -> ()){
        guard let url = Bundle.main.url(forResource: "subjects", withExtension: "json") else { fatalError("no file") }
        do{
            let json = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            decoder.userInfo[CodingUserInfoKey.context!] = managedObjectContext
            do{
                let subjects = try decoder.decode([Subject].self, from: json)
                /
                /
                for subject in subjects where subject.parentid != 0{
                    guard let parentid = subject.parentid else { fatalError("Can not get parentid") }
                    guard let parent = (subjects.filter { $0.id ==  parentid }.first) else { fatalError("Can not get parent") }
                    subject.parent = parent
                }
                do {
                    try managedObjectContext.save()
                        completion()
                } catch {
                    print("Error")
                       completion()
                }
            }catch{
                print("Error")
                completion()
            }
        } catch {
            print("Error")
            completion()
        }
    }
}


import Foundation
import CoreData
class ExportManager{
    var exportHandler: (() -> Void)!

    private var managedObjectContext: NSManagedObjectContext!
   
    private var id : Int16 = 0
   
    private var subjects : [Subject] = []
    private var processedSubjects : [Subject] = []
   
    init(managedObjectContext:NSManagedObjectContext){
        self.managedObjectContext = managedObjectContext
    }
   
    func exportData(){
        executeFetchRequest(){ [weak self] in
            guard let strongSelf = self else { return }
            print("strongSelf.subjects.count: \(strongSelf.subjects.count)")
            strongSelf.processRecords()
        }
    }
  
    private func executeFetchRequest(completion:@escaping () -> ()){
        let fetchRequest: NSFetchRequest<Subject> = Subject.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "parent == NULL")
        let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        fetchRequest.returnsObjectsAsFaults = false
        fetchRequest.relationshipKeyPathsForPrefetching = ["childs"]
        managedObjectContext.perform {
            self.subjects = try! fetchRequest.execute()
            completion()
        }
    }
   
    private func processRecords() {
        if subjects.count > 0{
            print("Processing: \(subjects.count)")
           
            var unProcessedSubjects : [Subject] = []
           
            for subject in subjects{
               
                id +=  1
               
                subject.id = id
               
                processedSubjects.append(subject)
                let currentParentId = id
               
                if let childs = subject.childs?.allObjects{
                   
                    if !childs.isEmpty{
                       
                        let childsArrayOfSubjects = childs as! [Subject]
                       
                        for child in childsArrayOfSubjects{
                            id += 1
                            child.id = id
                            child.parentid = currentParentId
                            unProcessedSubjects.append(child)
                        }
                    }
                }
            }
           
            subjects = unProcessedSubjects
            processRecords()
        } else {
            print("Finish Processing: \(processedSubjects.count)")
            do{
                let documentDirectoryURL =  try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                let jsonURL = URL(fileURLWithPath: "subjects", relativeTo: documentDirectoryURL.appendingPathComponent("Subjects")).appendingPathExtension("json")
               
                print("jsonURL: \(jsonURL)")
               
                let jsonEncoder = JSONEncoder()
                jsonEncoder.outputFormatting = .prettyPrinted
                let jsonData = try jsonEncoder.encode(processedSubjects)
                try jsonData.write(to: jsonURL)
                exportHandler()
            } catch let error as NSError{
                print("Error: \(error.description)")
            }
        }
    }
}

Replies

For starters, your decoder object needs to have a managed object context, because that self.init(insertInto:) call with nil is going to produce non-functional objects.


Second, you're attempting to encode/decode a tree of objects. The only way you can do that is encoding the root (parent-most) object. Otherwise, you're looking at an exercise in learning why encoding/decoding a tree from a random node in the tree means that you need to build a reference table and do two-stage linking.

Thanks for your answer NotMyName.


About your first answer:


"For starters, your decoder object needs to have a managed object context, because that self.init(insertInto:) call with nil is going to produce non-functional objects."


You are right, it should be:


self.init(entity: entity, insertInto: managedObjectContext)



About your second answer:


"The only way you can do that is encoding the root (parent-most) object."


If I encode this object, How could I encode the childs relationship of this object to have the full tree encoded?


Fatal error: Optional<NSSet> does not conform to Encodable because NSSet does not conform to Encodable

Finally I found a solution for my issue and would like to share here.


Note that in the class Subject, var id: Int16? and var parentid: Int16? are not @NSManaged and do not exists in .xcdatamodel.


import Foundation
import CoreData
@objc(Subject)
public class Subject: NSManagedObject, Codable {
    public var id: Int16?
    public var parentid: Int16?
    @NSManaged public var longdescription: String?
    @NSManaged public var shortdescription: String?
    @NSManaged public var title: String?
    @NSManaged public var childs: NSSet?
    @NSManaged public var parent: Subject?

    enum CodingKeys: String, CodingKey {
        case id
        case parentid
        case title
        case shortdescription
        case longdescription
    }

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

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id ?? 0, forKey: .id)
        try container.encode(parentid ?? 0, forKey: .parentid)
        try container.encode(title ?? "", forKey: .title)
        try container.encode(shortdescription ?? "", forKey: .shortdescription)
        try container.encode(longdescription ?? "", forKey: .longdescription)
    }

    public required convenience init(from decoder: Decoder) throws {
        guard let contextUserInfoKey = CodingUserInfoKey.context,
            let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let entity = NSEntityDescription.entity(forEntityName: "Subject", in: managedObjectContext) else {  fatalError("Failed to decode Subject!")  }

        self.init(entity: entity, insertInto: managedObjectContext)
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(Int16?.self, forKey: .id)
        parentid = try values.decode(Int16?.self, forKey: .parentid)
        title = try values.decode(String.self, forKey: .title)
        shortdescription = try values.decode(String.self, forKey: .shortdescription)
        longdescription = try values.decode(String.self, forKey: .longdescription)
    }
}
extension CodingUserInfoKey {
    static let context = CodingUserInfoKey(rawValue: "context")
}



import UIKit
import CoreData
import MessageUI
class RootViewController: UIViewController{

    @IBOutlet weak var tableView: UITableView!

    @IBAction func importData(_ sender: Any) {
        importManager.importData(){ [weak self] in
            guard let strongSelf = self else { return }
            do {
                try strongSelf.fetchedResultsController.performFetch()
                strongSelf.tableView.reloadData()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

    @IBAction func exportData(_ sender: Any) {
        exportManager.exportData()
    }

    private var importManager : ImportManager!
    private var exportManager : ExportManager!

    var managedObjectContext: NSManagedObjectContext!

    var _fetchedResultsController: NSFetchedResultsController<Subject>?
    var fetchedResultsController: NSFetchedResultsController<Subject> {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
     
        let fetchRequest: NSFetchRequest<Subject> = Subject.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "parent == NULL")
        fetchRequest.returnsObjectsAsFaults = false
        fetchRequest.relationshipKeyPathsForPrefetching = ["childs"]
        fetchRequest.fetchBatchSize = 20
        let sortDescriptor = NSSortDescriptor(key: "title", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
        fetchRequest.sortDescriptors = [sortDescriptor]
        let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController
        do {
            try _fetchedResultsController!.performFetch()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
     
        return _fetchedResultsController!
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        importManager = ImportManager(managedObjectContext: managedObjectContext)
        exportManager = ExportManager(managedObjectContext: managedObjectContext)
        exportManager.exportHandler = {
            print("Finish export data")
        }
    }
}
extension RootViewController : UITableViewDelegate{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
        tableView.deselectRow(at: indexPath, animated: false)
    }
}
extension RootViewController : UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
        return fetchedResultsController.fetchedObjects?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
     
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.reuseIdentifier, for: indexPath) as? TableViewCell else { fatalError("Unexpected cell type at \(indexPath)") }

        let subject = fetchedResultsController.object(at: indexPath)
        cell.configure(for: subject)
     
        return cell
    }
}
extension RootViewController : NSFetchedResultsControllerDelegate {
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        switch type {
        case .insert:
            tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
        case .delete:
            tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
        default:
            return
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
            if let indexPath = indexPath {
                guard let cell = tableView.cellForRow(at: indexPath) as? TableViewCell else { fatalError("Can not get Cell") }
                let subject = fetchedResultsController.object(at: indexPath)
                cell.configure(for: subject)
            }
        case .move:
            tableView.moveRow(at: indexPath!, to: newIndexPath!)
        }
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }
}


import Foundation
import CoreData
class ImportManager{
    var managedObjectContext: NSManagedObjectContext!
  
    init(managedObjectContext:NSManagedObjectContext){
        self.managedObjectContext = managedObjectContext
    }

    func importData(completion:@escaping () -> ()){
        guard let url = Bundle.main.url(forResource: "subjects", withExtension: "json") else { fatalError("no file") }
        do{
            let json = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            decoder.userInfo[CodingUserInfoKey.context!] = managedObjectContext
            do{
                let subjects = try decoder.decode([Subject].self, from: json)
                /
                /
                for subject in subjects where subject.parentid != 0{
                    guard let parentid = subject.parentid else { fatalError("Can not get parentid") }
                    guard let parent = (subjects.filter { $0.id ==  parentid }.first) else { fatalError("Can not get parent") }
                    subject.parent = parent
                }
                do {
                    try managedObjectContext.save()
                        completion()
                } catch {
                    print("Error")
                       completion()
                }
            }catch{
                print("Error")
                completion()
            }
        } catch {
            print("Error")
            completion()
        }
    }
}


import Foundation
import CoreData
class ExportManager{
    var exportHandler: (() -> Void)!

    private var managedObjectContext: NSManagedObjectContext!
   
    private var id : Int16 = 0
   
    private var subjects : [Subject] = []
    private var processedSubjects : [Subject] = []
   
    init(managedObjectContext:NSManagedObjectContext){
        self.managedObjectContext = managedObjectContext
    }
   
    func exportData(){
        executeFetchRequest(){ [weak self] in
            guard let strongSelf = self else { return }
            print("strongSelf.subjects.count: \(strongSelf.subjects.count)")
            strongSelf.processRecords()
        }
    }
  
    private func executeFetchRequest(completion:@escaping () -> ()){
        let fetchRequest: NSFetchRequest<Subject> = Subject.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "parent == NULL")
        let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        fetchRequest.returnsObjectsAsFaults = false
        fetchRequest.relationshipKeyPathsForPrefetching = ["childs"]
        managedObjectContext.perform {
            self.subjects = try! fetchRequest.execute()
            completion()
        }
    }
   
    private func processRecords() {
        if subjects.count > 0{
            print("Processing: \(subjects.count)")
           
            var unProcessedSubjects : [Subject] = []
           
            for subject in subjects{
               
                id +=  1
               
                subject.id = id
               
                processedSubjects.append(subject)
                let currentParentId = id
               
                if let childs = subject.childs?.allObjects{
                   
                    if !childs.isEmpty{
                       
                        let childsArrayOfSubjects = childs as! [Subject]
                       
                        for child in childsArrayOfSubjects{
                            id += 1
                            child.id = id
                            child.parentid = currentParentId
                            unProcessedSubjects.append(child)
                        }
                    }
                }
            }
           
            subjects = unProcessedSubjects
            processRecords()
        } else {
            print("Finish Processing: \(processedSubjects.count)")
            do{
                let documentDirectoryURL =  try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                let jsonURL = URL(fileURLWithPath: "subjects", relativeTo: documentDirectoryURL.appendingPathComponent("Subjects")).appendingPathExtension("json")
               
                print("jsonURL: \(jsonURL)")
               
                let jsonEncoder = JSONEncoder()
                jsonEncoder.outputFormatting = .prettyPrinted
                let jsonData = try jsonEncoder.encode(processedSubjects)
                try jsonData.write(to: jsonURL)
                exportHandler()
            } catch let error as NSError{
                print("Error: \(error.description)")
            }
        }
    }
}