将JSON保存为String作为String并使用String创建对象数组

时间:2018-08-16 03:36:39

标签: ios arrays json swift core-data

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

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

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数组的函数:

    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的值,并将其作为响应:

  

[{“名称”:“示例显示2”,“ ID”:“ 2”,“描述”:“此...

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

  

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

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

  

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

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

2 个答案:

答案 0 :(得分:1)

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

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

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

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

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

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可以在应用程序中的任何位置进行访问都是很常见的。我在基本操作中包括了一个:

Image of Core Data Model

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()。这里最重要的一行是

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

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

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

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")
        }

    }



}

View Hierarchy

答案 1 :(得分:0)

尝试一下对我有用

// Convert JSON to String
func jsonToString(json: AnyObject)->String{
    do {
        let data1 =  try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted) // first of all convert json to the data
        let convertedString = String(data: data1, encoding: String.Encoding.utf8) // the data will be converted to the string
        return convertedString! // <-- here is ur string

    } catch let myJSONError {
        print(myJSONError)
    }

    return ""
}

// Convert JSON String to Dict
func convertToDictionary(text: String) -> NSDictionary!{
    if let data = text.data(using: .utf8) {
        do {

            return try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary
        } catch {
            print(error.localizedDescription)
        }
    }
    return nil
}