将JSON保存为String作为String并使用String创建对象数组 [英] Save json to CoreData as String and use the String to create array of objects

查看:109
本文介绍了将JSON保存为String作为String并使用String创建对象数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为一个广播电台创建一个应用程序,我想将显示"对象存储到一个数组中.我使用网络服务器提供json数据来填充数组,但是我想将此json数据作为字符串存储到CoreData中,以便对数组的访问不依赖于互联网连接.因此,我想在应用启动时更新CoreData中的字符串,但要基于存储在CoreData中的字符串而不是基于Web服务器上的json数据创建一个数组.

I am creating an app for a radio station and I want to store "show" objects into an array. I use a webserver to supply json data to populate the array, but I want to store this json data into CoreData as a string so that access to the array doesn't depend on internet connection. Therefore, I want to update the string in CoreData on app launch, but create an array based off of the string stored in CoreData not on the json data from the webserver.

这是我的功能,用于从网络服务器下载json数据并将其存储为字符串:

Here's my function to download the json data from the webserver and store it into a string:

func downloadShows() {
    let urlPath = "http://dogradioappdatabase.com/shows.php"

    guard let url = URL(string: urlPath) else {return}

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let dataResponse = data,
            error == nil else {
                print(error?.localizedDescription ?? "Response Error")
                return }

        let jsonAsString = self.jsonToString(json: dataResponse)

        DispatchQueue.main.async {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

            let task2 = WebServer(context: context) // Link Task & Context
            task2.showsArray = jsonAsString
            print(jsonAsString)
            (UIApplication.shared.delegate as! AppDelegate).saveContext()
        }
    }
    task.resume()

}    

func jsonToString(json: Data) -> String {
    let convertedString: String

        convertedString = String(data: json, encoding: String.Encoding.utf8)! // the data will be converted to the string
    return convertedString
}

这是从CoreData提取的json创建shows数组的函数:

Here's the function to create the shows array from the fetched json from CoreData:

    func createShowsArray () -> Array<ShowModel> {
    var array: Array<ShowModel> = Array()
    var tasks: [WebServer] = []

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    do {
        tasks = try context.fetch(WebServer.fetchRequest())
    }
    catch {
        print("Fetching Failed")
    }

    let arrayAsString: String = tasks[0].showsArray!
    print(arrayAsString)
    do {
        let data1 = arrayAsString.data(using: .utf8)!
        let decoder = JSONDecoder()
        array = try decoder.decode([ShowModel].self, from:
            data1)

    } catch let parsingError {
        print("Error", parsingError)
    }

    return array
}

但是,这不能正确地将数据加载到数组中.我在downloadShows()函数(jsonAsString)中打印了保存到CoreData的值,并将其作为响应:

However, this does not correctly load the data into an array. I printed the value I saved to CoreData in the downloadShows() function (jsonAsString) and got this as a response:

[{名称":示例节目2","ID":"2",描述":此...

[{"Name":"Example Show 2","ID":"2","Description":"This ...

但是当我在createShowsArray()函数(arrayAsString)中从CoreData获取字符串时,它已经添加了"DOG_Radio.ShowModel"

But when I fetched the string from CoreData in the createShowsArray() function (arrayAsString), it had added "DOG_Radio.ShowModel"

[DOG_Radio.ShowModel(Name:"Example Show 2",ID:"2",说明:"This ...

[DOG_Radio.ShowModel(Name: "Example Show 2", ID: "2", Description: "This ...

JSON解码器不会将arrayAsString解码为实际的数组.它把这个扔回去:

The JSON Decoder does not decode arrayAsString into an actual array. It throws this back:

错误dataCorrupted(Swift.DecodingError.Context(codingPath:[],debugDescription:给定的数据不是有效的JSON.",底层错误:可选(错误域= NSCocoaErrorDomain代码= 3840字符1周围的无效值")UserInfo = {NSDebugDescription =字符1周围的值无效.})))

Error dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 1." UserInfo={NSDebugDescription=Invalid value around character 1.})))

很抱歉,我的问题很长,我只是不知道如何使用CoreData将json保存为String,然后稍后将该String转换为数组

Sorry for the long question, I just don't know how to use CoreData to save json as String then convert that String into an array later

推荐答案

将json数据或整个原始数据"存储到CoreData中是一种不好的做法.而是将Show本身存储为NSManagedObject. 为此,您可以将JSON数据转换为一个对象(看起来就像您已经在做的那样),然后根据它们创建CoreData NSManagedObjects.

It's a bad practice to store json data or 'whole raw data' into CoreData. Instead Store the Show itself as a NSManagedObject. You can do this by converting the JSON data to an Object (which it looks like you are already doing), then creating CoreData NSManagedObjects from them.

实际上,如果您可以轻松地从JSON转换数据,则在保存到CoreData之前无需将其转换为字符串.您可以简单地将Data存储为NSData,即transformablebinary data,如果以后无法成功提取到服务器,则可以将其重新转换.

Realistically if you have no trouble converting the data from JSON there is no need to convert it to a string before saving to CoreData. You can simply store the Data as NSData, i.e. transformable or binary data and reconvert it later if your fetch to the server fails.

但是,从长远来看,这并不是那么可靠,并且更难处理.数据可能已损坏和/或格式错误.

However, thats not that reliable and much harder to work with in the long run. The data could be corrupt and/or malformed.

简而言之,您需要一个数据模型和一个JSON可读数据结构,您可以将其转换为数据模型以供CoreData管理.在以后要允许用户更新,删除,保存或过滤单个Show时,这将变得很重要.

In short, you need a Data Model and a JSON readable Data Structure you can Convert to your Data Model to for CoreData to manage. This will become important later when you want to allow the user to update, remove, save or filter individual Show's.

Codable将允许您使用JSONDecoder().decode(_:from:)从JSON转换为Struct.

Codable will allow you to covert from JSON to a Struct with JSONDecoder().decode(_:from:).

ShowModelCodeable.swift

import Foundation

struct ShowModelCodeable: Codable {
    var name: String?
    var description: String?
    var producer: String?
    var thumb: String?
    var live: String?
    var banner: String?
    var id: String?

    enum CodingKeys: String, CodingKey {
        case name = "Name"
        case id = "ID"
        case description = "Description"
        case producer = "Producer"
        case thumb = "Thumb"
        case live = "Live"
        case banner = "Banner"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        description = try values.decode(String.self, forKey: .description)
        producer = try values.decode(String.self, forKey: .producer)
        thumb = try values.decode(String.self, forKey: .thumb)
        live = try values.decode(String.self, forKey: .live)
        banner = try values.decode(String.self, forKey: .banner)
        id = try values.decode(String.self, forKey: .id)

    }

    func encode(to encoder: Encoder) throws {

    }
}

接下来,我们需要一个Core Data Stack和一个CoreData Entity.将Core Data Stack创建为Class Singleton可以在应用程序中的任何位置进行访问都是很常见的.我在基本操作中包括了一个:

Next, We'll need a Core Data Stack and a CoreData Entity. Its very common to create a Core Data Stack as a Class Singleton that can be accessed anywhere in your app. I've included one with basic operations:

DatabaseController.Swift

import Foundation
import CoreData

class DatabaseController {

    private init() {}

    //Returns the current Persistent Container for CoreData
    class func getContext () -> NSManagedObjectContext {
        return DatabaseController.persistentContainer.viewContext
    }


    static var persistentContainer: NSPersistentContainer = {
        //The container that holds both data model entities
        let container = NSPersistentContainer(name: "StackOverflow")

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */

                //TODO: - Add Error Handling for Core Data

                fatalError("Unresolved error \(error), \(error.userInfo)")
            }


        })
        return container
    }()

    // MARK: - Core Data Saving support
    class func saveContext() {
        let context = self.getContext()
        if context.hasChanges {
            do {
                try context.save()
                print("Data Saved to Context")
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate.
                //You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

    /* Support for GRUD Operations */

    // GET / Fetch / Requests
    class func getAllShows() -> Array<ShowModel> {
        let all = NSFetchRequest<ShowModel>(entityName: "ShowModel")
        var allShows = [ShowModel]()

        do {
            let fetched = try DatabaseController.getContext().fetch(all)
            allShows = fetched
        } catch {
            let nserror = error as NSError
            //TODO: Handle Error
            print(nserror.description)
        }

        return allShows
    }

    // Get Show by uuid
    class func getShowWith(uuid: String) -> ShowModel? {
        let requested = NSFetchRequest<ShowModel>(entityName: "ShowModel")
        requested.predicate = NSPredicate(format: "uuid == %@", uuid)

        do {
            let fetched = try DatabaseController.getContext().fetch(requested)

            //fetched is an array we need to convert it to a single object
            if (fetched.count > 1) {
                //TODO: handle duplicate records
            } else {
                return fetched.first //only use the first object..
            }
        } catch {
            let nserror = error as NSError
            //TODO: Handle error
            print(nserror.description)
        }

        return nil
    }

    // REMOVE / Delete
    class func deleteShow(with uuid: String) -> Bool {
        let success: Bool = true

        let requested = NSFetchRequest<ShowModel>(entityName: "ShowModel")
        requested.predicate = NSPredicate(format: "uuid == %@", uuid)


        do {
            let fetched = try DatabaseController.getContext().fetch(requested)
            for show in fetched {
                DatabaseController.getContext().delete(show)
            }
            return success
        } catch {
            let nserror = error as NSError
            //TODO: Handle Error
            print(nserror.description)
        }

        return !success
    }

}

// Delete ALL SHOWS From CoreData
class func deleteAllShows() {
    do {
        let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "ShowModel")
        let deleteALL = NSBatchDeleteRequest(fetchRequest: deleteFetch)

        try DatabaseController.getContext().execute(deleteALL)
        DatabaseController.saveContext()
    } catch {
        print ("There is an error in deleting records")
    }
}

最后,我们需要一种获取JSON数据并将其转换为对象,然后显示的方法.请注意,当按下更新按钮时,它将触发getDataFromServer().最重要的一行是

Finally, we need a way to get the JSON data and convert it to our Objects, then Display it. Note that when the update button is pressed, it fires getDataFromServer(). The most important line here is

self.newShows = try JSONDecoder().decode([ShowModelCodeable].self, from: dataResponse)

正在从服务器下拉显示,并将其转换为ShowModelCodeable对象.设置newShows后,它将运行didSet中的代码,在这里您可以删除上下文中的所有对象,然后运行addNewShowsToCoreData(_:)创建要保存在上下文中的新NSManagedObjects.

The Shows are being pulled down from your Server, and converted to ShowModelCodeable Objects. Once newShows is set it will run the code in didSet, here you can delete all the Objects in the context, then run addNewShowsToCoreData(_:) to create new NSManagedObjects to be saved in the context.

我创建了一个基本的视图控制器,并以编程方式添加了tableView来管理数据.在这里,Shows是CoreData的NSManagedObject数组,而newShows是从服务器请求中获取的json编码的新对象.

I've created a basic view controller and programmatically added a tableView to manage the data. Here, Shows is your NSManagedObject array from CoreData, and newShows are new objects encoded from json that we got from the server request.

ViewController.swift

import Foundation
import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    // Properties
    var Shows:[ShowModel]?

    var newShows:[ShowModelCodeable]? {
        didSet {
            // Remove all Previous Records
            DatabaseController.deleteAllShows()
            // Add the new spots to Core Data Context
            self.addNewShowsToCoreData(self.newShows!)
            // Save them to Core Data
            DatabaseController.saveContext()
            // Reload the tableView
            self.reloadTableView()
        }
    }

    // Views
    var tableView: UITableView = {
        let v = UITableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    lazy var updateButton: UIButton = {
        let b = UIButton()
        b.translatesAutoresizingMaskIntoConstraints = false
        b.setTitle("Update", for: .normal)
        b.setTitleColor(.black, for: .normal)
        b.isEnabled = true
        b.addTarget(self, action: #selector(getDataFromServer), for: .touchUpInside)
        return b
    }()

    override func viewWillAppear(_ animated: Bool) {
        self.Shows = DatabaseController.getAllShows()
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.tableView.delegate = self
        self.tableView.dataSource = self
        self.tableView.register(ShowCell.self, forCellReuseIdentifier: ShowCell.identifier)

        self.layoutSubViews()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    //TableView -
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return DatabaseController.getAllShows().count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        // 100
        return ShowCell.height()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = self.tableView.dequeueReusableCell(withIdentifier: ShowCell.identifier) as! ShowCell

        self.Shows = DatabaseController.getAllShows()

        if Shows?.count != 0 {
            if let name = Shows?[indexPath.row].name {
                cell.nameLabel.text = name
            }

            if let descriptionInfo = Shows?[indexPath.row].info {
                cell.descriptionLabel.text = descriptionInfo
            }
        } else {
            print("No shows bros")
        }


        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Show the contents
        print(Shows?[indexPath.row] ?? "No Data For this Row.")

    }

    func reloadTableView() {
        DispatchQueue.main.async {
         self.tableView.reloadData()
        }
    }


    func layoutSubViews() {
        let guide = self.view.safeAreaLayoutGuide
        let spacing: CGFloat = 8

        self.view.addSubview(tableView)
        self.view.addSubview(updateButton)

        updateButton.topAnchor.constraint(equalTo: guide.topAnchor, constant: spacing).isActive = true
        updateButton.leftAnchor.constraint(equalTo: guide.leftAnchor, constant: spacing * 4).isActive = true
        updateButton.rightAnchor.constraint(equalTo: guide.rightAnchor, constant: spacing * -4).isActive = true
        updateButton.heightAnchor.constraint(equalToConstant: 55.0).isActive = true

        tableView.topAnchor.constraint(equalTo: updateButton.bottomAnchor, constant: spacing).isActive = true
        tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: spacing).isActive = true
    }


    @objc func getDataFromServer() {
        print("Updating...")
        let urlPath = "http://dogradioappdatabase.com/shows.php"

        guard let url = URL(string: urlPath) else {return}

        let task = URLSession.shared.dataTask(with: url) {
            (data, response, error) in
                guard let dataResponse = data, error == nil else {
                        print(error?.localizedDescription ?? "Response Error")
                return }

            do {
                self.newShows = try JSONDecoder().decode([ShowModelCodeable].self, from: dataResponse)
            } catch {
                print(error)
            }

        }

        task.resume()
    }




    func addNewShowsToCoreData(_ shows: [ShowModelCodeable]) {

        for show in shows {
            let entity = NSEntityDescription.entity(forEntityName: "ShowModel", in: DatabaseController.getContext())
            let newShow = NSManagedObject(entity: entity!, insertInto: DatabaseController.getContext())

            // Create a unique ID for the Show.
            let uuid = UUID()
            // Set the data to the entity
            newShow.setValue(show.name, forKey: "name")
            newShow.setValue(show.description, forKey: "info")
            newShow.setValue(show.producer, forKey: "producer")
            newShow.setValue(show.thumb, forKey: "thumb")
            newShow.setValue(show.live, forKey: "live")
            newShow.setValue(show.banner, forKey: "banner")
            newShow.setValue(show.id, forKey: "id")
            newShow.setValue(uuid.uuidString, forKey: "uuid")
        }

    }



}

这篇关于将JSON保存为String作为String并使用String创建对象数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆