DJFriar
DJFriar

Reputation: 340

How do I make JSON data persistent for offline use (Swift 4)

In my app, I am currently processing a JSON file hosted on a website. I want to make it so that if a user opens the app and has no data connection, it would use the last downloaded data. I'm not entirely sure how to go about this.

My original thought was to download the JSON itself, and then simply compare version numbers on load, download it if newer, and then code the app to always read the local file. However, I think I'd rather it just look at the website, store the JSON contents locally, and then upon subsequent opens, just compare the version and update its local data if the version on the site is newer.

Is there any advantage to one method over the other?

Where should I start looking for information on making the data I load from the website hosted JSON stored locally and persistent? I assume CoreData would be best for this, but my Google-fu isn't finding much on how to take JSON into Core Data.

EDIT: Per Starksy's suggestion, I added the following to my app, but I'm getting an error on the let jsonData = try ... line.

func saveJSONFile() {
    let itemName = "myJSONFromWeb"
    let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
    do {
        let directory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
        let fileURL = directory.appendingPathComponent(itemName)
        // let jsonData = try JSONDecoder().decode(hostedJSONFile.self, from: Data)
        let jsonData = try JSONSerialization.jsonObject(with: hostedJSONFile, options: .mutableContainers)
        try jsonData.write(to: fileURL, options: .atomic)
        //Save the location of your JSON file to UserDefaults
        defaults.set(fileURL, forKey: "pathForJSON")
    } catch {
        print(error)
    }
}

The error is

Ambiguous reference to member 'jsonObject(with:options:)'

When I google that, I see another SO post that says I should use JSONDecoder().decode instead since I'm using Swift 4, but I'm not sure how to convert the above code to use that method instead.

Upvotes: 1

Views: 4827

Answers (2)

Starsky
Starsky

Reputation: 2028

I would recommend taking a look at Realm if you ever need local storage. It is an alternative to Core Data, but is simpler to use: https://realm.io/docs/swift/latest/

For this task specifically, I wouldn't save to CoreData or Realm, but directly to the app directory, and save the version and location of the file in the UserDefaults.

1) (UPDATED ANSWER)

//Download the file from web and save it to the local directory on the device
let hostedJSONFile = "http://tourofhonor.com/BonusData.json"
let jsonURL = URL(string: hostedJSONFile)
let defaults = UserDefaults.standard
let itemName = "myJSONFromWeb"
do {
    let directory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
    let fileURL = directory.appendingPathComponent(itemName)
    let jsonData = try Data(contentsOf: jsonURL!)
    try jsonData.write(to: fileURL, options: .atomic)
    //Save the location of your JSON file to UserDefaults
    defaults.set(fileURL, forKey: "pathForJSON")
} catch {
    print(error)
}

2) Save the version of your JSON (if it has a version coming from the web) to UserDefaults:

defaults.set(1, forKey: "jsonVersion")

3) When you launch your app, you want to verify the current jsonVersion to the version you saved in document directory:

let currentVersion = //...get the current version from your JSON file from web.
let existingVersion = defaults.integer(forKey: "jsonVersion")
if currentVersion != existingVersion {
   //Download the newest JSON and save it again to documents directory for future use. Also, use it now for your needs.
} else {
   //Retrieve the existing JSON from documents directory
   let fileUrl = defaults.url(forKey: "pathForJSON")
   do {
       let jsonData2 = try Data(contentsOf: fileUrl!, options: [])
       let myJson = try JSONSerialization.jsonObject(with: jsonData2, options: .mutableContainers)
       //...do thing with you JSON file
       print("My JSON: ", myJson)
   } catch {
       print(error)
   }

Upvotes: 7

NiTrOs
NiTrOs

Reputation: 359

its not the best answer but what you can do is save it in user defaults

UserDefaults.standard.set(data, forKey: "jsonData")

do not save the object, just the data that you get from your session, then make a prase method

func parseJSON (dataRecieved : Data) -> [ItemsJSON] {
    var jsonArray = [ItemsJSON]()
    do {
    let decode = JSONDecoder()
    let json = try decode.decode(CustomJSONClass.self, from: dataRecieved)
        if json.error == true {
            print("error from server")
        }
        for index in json.data {
            jsonArray.append(index)
        }
    }
    catch {
        print("error in appending json")
    }
    return jsonArray
}

Upvotes: 0

Related Questions