How to create Core Data object to loaded Json data?

115 Views Asked by At

I'm trying to figure out how to create a Core Data object from Json data (provided by Financial Modeling Prep).

The application is structured this way:

  • An API_SERVICE class with functions like this:
func readJsonIncomeStatementFMP(keywords: String) -> AnyPublisher<[IncomeStatementFMP], Error>  {

        let result = parseQuery(text: keywords)
        var symbol = String()
        
        switch result {
        case .success(let query):
            symbol = query
        case .failure(let error):
            return Fail(error: error).eraseToAnyPublisher()
        }
        
        let urlString = "https://financialmodelingprep.com/api/v3/income-statement/\(symbol)?apikey=\(API_KEY)"
        
        let urlResult = parseURL(urlString: urlString)
        switch urlResult {
        case .success(let url):
            return URLSession.shared.dataTaskPublisher(for: url)
                .map({$0.data})
                .decode(type: [IncomeStatementFMP].self, decoder: JSONDecoder())
                .receive(on: RunLoop.main)
                .eraseToAnyPublisher()
        
        case .failure(let error):
            return Fail(error: error).eraseToAnyPublisher()
        }
    }
  • A SCREENER view in which the API_SERVICE is called:
var body: some View {
        @Environment(\.managedObjectContext) private var viewContext
    
            //CoreData
            @FetchRequest(entity: Entreprise.entity(), sortDescriptors: [NSSortDescriptor(key: "companyName", ascending: true)]) var entreprises: FetchedResults<Entreprise>
        @State var subscribers = Set<AnyCancellable>()
            @ObservedObject var apiServiceFMP = APIServiceFMP()
            @State var resultScreener: [ResultScreener]?
        @State var incomeStatementFMP: [IncomeStatementFMP]?
        
        ZStack{
            VStack{
                ScrollView(.horizontal){

                VStack{
                    Button(action: {
                        handleSelectionStockScreenerMultipleProperties(exchange: exchange, country: country, marketCap: marketCap)
                    }){
                        Text("Lancer le screen")
                            .fontWeight(.semibold)
                    }
                    .padding()
                    .foregroundColor(.white)
                    .background(Color(.green))
                    .cornerRadius(10)
                }
                if resultScreener != nil{
                    VStack{
                        List(searchResults, id: \.id){ results in
                            HStack{
                                Text(results.symbol)
                                Divider()
                                Text(results.companyName)
                                Divider()
                                Button(action: {
                                    Task{
                                        await handleSelectionIncomeStatementForFMP(for: results.symbol)
                                        await handleSelectionBalanceSheetForFMP(for: results.symbol)
                                        await handleSelectionCashFlowForFMP(for: results.symbol)
                                        await handleSelectionCompanyOutlookForFMP(for: results.symbol)
                                        await handleSelectionHistoricalDailyPriceFMP(for: results.symbol)
                                    }
                                }){
                                    Text("Infos").foregroundColor(.white)
                                }.buttonStyle(.plain)
                                
                                
                                if incomeStatementFMP != nil && balanceSheetFMP != nil && cashFlowFMP != nil && companyOutlookFMP != nil && historicalDailyPriceFMP != nil && incomeStatementFMP!.count > 0 {
                                    if incomeStatementFMP![0].symbol == results.symbol{
                                        Button(action: {
                                            Task{
                                                await handleSelectionIncomeStatementForFMP(for: results.symbol)
                                                await handleSelectionBalanceSheetForFMP(for: results.symbol)
                                                await handleSelectionCashFlowForFMP(for: results.symbol)
                                                await handleSelectionCompanyOutlookForFMP(for: results.symbol)
                                                await handleSelectionHistoricalDailyPriceFMP(for: results.symbol)
                                            }
                                            addEntreprise(incomeStatementFMP: incomeStatementFMP!, balanceSheetFMP: balanceSheetFMP!, cashFlowFMP: cashFlowFMP!, companyOutlookFMP: companyOutlookFMP!, ticker: results.symbol)
                                        }){
                                            Text("Ajout BDD").foregroundColor(.white)
                                        }.buttonStyle(.plain)
                                        .padding()
                                        .background(.blue)
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

private func handleSelectionIncomeStatementForFMP(for symbol: String) async{
 
        apiServiceFMP.readJsonIncomeStatementFMP(keywords: symbol).sink { (completionResult) in
            
            switch completionResult {
            case .failure(let error):
                print(error)
            case .finished: break
            }
        } receiveValue: { (incomeStatementResults) in
            self.incomeStatementFMP = incomeStatementResults
        }.store(in: &subscribers)
    }
private func addEntreprise(incomeStatementFMP: [IncomeStatementFMP], balanceSheetFMP: [BalanceSheetFMP], cashFlowFMP: [CashFlowFMP], companyOutlookFMP: [CompanyOutlookFMP], ticker: String){
        if incomeStatementFMP.count > 0 && balanceSheetFMP.count > 0 && cashFlowFMP.count > 0 {
            if companyOutlookFMP[0].profile.symbol == ticker && incomeStatementFMP.count > 0 && incomeStatementFMP[0].symbol == ticker && balanceSheetFMP[0].symbol == ticker && cashFlowFMP[0].symbol == ticker{
                if entreprises.filter({$0.ticker == ticker}).count != 0 {

                    for entreprise in entreprises.filter({$0.ticker == ticker}){
                        if entreprise.incomes!.count == incomeStatementFMP.count {
                            ///Si les données fondamentales n'ont pas changé, on arrête
                            return
                        }
                        else {
                            ///Si les données fondamentales sont plus nombreuses que celles déjà enregistrées
                            ///Il mettre à jour les données de l'objet existant
                            print("Il faut intégrer la méthode pour modifier l'objet sans le recréer")
                        }
                    }
                } else {
                    createNewEntreprise(companyOutlookFMP: companyOutlookFMP, incomeStatementFMP: incomeStatementFMP, balanceSheetFMP: balanceSheetFMP, cashFlowFMP: cashFlowFMP, historicalDailyPriceFMP: historicalDailyPriceFMP!, ticker: ticker)
                }
            }
        } else {
            print("Il n'y a pas assez de données, pas d'intérêt d'enregistrer l'entreprise")
        }
    }
    
    func createNewEntreprise(companyOutlookFMP: [CompanyOutlookFMP], incomeStatementFMP: [IncomeStatementFMP], balanceSheetFMP: [BalanceSheetFMP], cashFlowFMP: [CashFlowFMP], historicalDailyPriceFMP: [HistoricalDailyPriceFMP], ticker: String){
        if companyOutlookFMP[0].profile.symbol == ticker && incomeStatementFMP[0].symbol == ticker && balanceSheetFMP[0].symbol == ticker && cashFlowFMP[0].symbol == ticker && historicalDailyPriceFMP[0].symbol == ticker{
            let newEntreprise = Entreprise(context: viewContext)
            newEntreprise.ticker = companyOutlookFMP[0].profile.symbol
            newEntreprise.industry = companyOutlookFMP[0].profile.industry
            newEntreprise.sector = companyOutlookFMP[0].profile.sector
            newEntreprise.companyName = companyOutlookFMP[0].profile.companyName
            
            newEntreprise.currency = companyOutlookFMP[0].profile.currency
            newEntreprise.region = companyOutlookFMP[0].profile.country
            newEntreprise.exchange = companyOutlookFMP[0].profile.exchangeShortName
            
            let s = companyOutlookFMP[0].profile.isin
            let any : Any? = s
            if any != nil {
                newEntreprise.isin = companyOutlookFMP[0].profile.isin
            } else {
                newEntreprise.isin = "Pas de ISIN"
            }

            newEntreprise.lastPrice = Array(getLastDailyPrice(historicalDailyPrice: historicalDailyPriceFMP)).sorted(by: >)[0].value
            newEntreprise.dateLastPrice = Array(getLastDailyPrice(historicalDailyPrice: historicalDailyPriceFMP)).sorted(by: >)[0].key
            
            //Ajouter les données fondamentales
            for i in addIncomeToEnterprise(incomeStatementFMP: incomeStatementFMP){
                newEntreprise.addToIncomes(i)
                //On enregistre les Income Statement
                do {
                    try viewContext.save()
                } catch {
                    let nsError = error as NSError
                    fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                }
            }
            for i in addBalanceToEnterprise(balanceSheetFMP: balanceSheetFMP){
                newEntreprise.addToBalances(i)
                //On enregistre les Balance Sheet
                do {
                    try viewContext.save()
                } catch {
                    let nsError = error as NSError
                    fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                }
            }
            for i in addCashToEnterprise(cashFlowFMP: cashFlowFMP){
                newEntreprise.addToCashFlows(i)
                //On enregistre les Cash Flow
                do {
                    try viewContext.save()
                } catch {
                    let nsError = error as NSError
                    fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                }
            }
            //On enregistre l'objet newEntreprise avec toutes les Relationships
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

I think we should use the notion of Async Await.

I would like to load the data with one click on "Ajout BDD".

This is where it doesn't work perfectly -> I have to click on the "Info" button first which calls the API_SERVICE functions before I can press the "Ajout BDD" button which is used to create Core objects Data.

Why when I directly press "Ajout BDD" nothing happens? If I press twice it adds the object in Core Data.

Thanks for your help

1

There are 1 best solutions below

0
bernardlf On

I found a solution that needs to be improved but it works. Of the four variables I want to monitor, I added a DidSet :

@State var incomeStatementFMP: [IncomeStatementFMP]?{
    didSet{
        observeEntreprise()
    }
}
@State var balanceSheetFMP: [BalanceSheetFMP]?{
    didSet{
        observeEntreprise()
    }
}
@State var cashFlowFMP: [CashFlowFMP]?{
    didSet{
        observeEntreprise()
    }
}
@State var companyOutlookFMP: [CompanyOutlookFMP]?{
    didSet{
        observeEntreprise()
    }
}

The rest is handled by the function observeEntreprise :

func observeEntreprise(){
    if incomeStatementFMP != nil && balanceSheetFMP != nil && cashFlowFMP != nil && companyOutlookFMP != nil && historicalDailyPriceFMP != nil{
        let newEntreprise = Entreprise(context: viewContext)
        ............
}