elpita
elpita

Reputation: 1115

How to save an array as a json file in Swift?

I'm new at swift and I'm having trouble with this. so what i need to do is save this array as a json file in the document folder of the iphone.

var levels = ["unlocked", "locked", "locked"]

and then later on be able to read it back into another array. Could someone please tell me how to do this? or provided with the exact code to accomplish this.

EDITED: I found one example of this. This is how they set up the data:

 "[ {"person": {"name":"Dani","age":"24"}}, {"person": {"name":"ray","age":"70"}} ]" 

and the you can access it this way:

 if let item = json[0] 
   { if let person = item["person"] 
     { if let age = person["age"] 
      { println(age) } } }

But I need to be able to do the same but from a file that is saved on the document folder.

Upvotes: 34

Views: 66146

Answers (6)

Brody Higby
Brody Higby

Reputation: 137

Here is Isuru's answer in Swift 4.2. This works in a playground:

let documentsDirectoryPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let documentsDirectoryPath = NSURL(string: documentsDirectoryPathString)!

let jsonFilePath = documentsDirectoryPath.appendingPathComponent("test.json")
let fileManager = FileManager.default
var isDirectory: ObjCBool = false

// creating a .json file in the Documents folder
if !fileManager.fileExists(atPath: (jsonFilePath?.absoluteString)!, isDirectory: &isDirectory) {
    let created = fileManager.createFile(atPath: jsonFilePath!.absoluteString, contents: nil, attributes: nil)
    if created {
        print("File created ")
    } else {
        print("Couldn't create file for some reason")
    }
} else {
    print("File already exists")
}

// creating an array of test data
var numbers = [String]()
for i in 0..<100 {
    numbers.append("Test\(i)")
}

// creating JSON out of the above array
var jsonData: NSData!
do {
    jsonData = try JSONSerialization.data(withJSONObject: numbers, options: JSONSerialization.WritingOptions()) as NSData
    let jsonString = String(data: jsonData as Data, encoding: String.Encoding.utf8)
    print(jsonString as Any)
} catch let error as NSError {
    print("Array to JSON conversion failed: \(error.localizedDescription)")
}

// Write that JSON to the file created earlier
//    let jsonFilePath = documentsDirectoryPath.appendingPathComponent("test.json")
do {
    let file = try FileHandle(forWritingTo: jsonFilePath!)
    file.write(jsonData as Data)
    print("JSON data was written to teh file successfully!")
} catch let error as NSError {
    print("Couldn't write to file: \(error.localizedDescription)")
}

Upvotes: 1

Mastergalen
Mastergalen

Reputation: 4389

In Swift 4 this is already built-in with JSONEncoder.

let pathDirectory = getDocumentsDirectory()
try? FileManager().createDirectory(at: pathDirectory, withIntermediateDirectories: true)
let filePath = pathDirectory.appendingPathComponent("levels.json")

let levels = ["unlocked", "locked", "locked"]
let json = try? JSONEncoder().encode(levels)

do {
     try json!.write(to: filePath)
} catch {
    print("Failed to write JSON data: \(error.localizedDescription)")
}

func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

The object you're trying to encode must conform to the Encodable protocol.

Read Apple's official guide on how to extend existing objects to be encodable.

Upvotes: 27

Prashant Tukadiya
Prashant Tukadiya

Reputation: 16416

Here is generic Swift solution

I have created generic class which allows to do it easily

//
//  OfflineManager.swift
// 
//
//  Created by Prashant on 01/05/18.
//  Copyright © 2018 Prashant. All rights reserved.
//

import UIKit

class OfflineManager: NSObject {

    static let sharedManager = OfflineManager()
    let LocalServiceCacheDownloadDir        = "LocalData"

    // Replace case as your naming 

    enum WSCacheKeys {
        case CampignList . 
        case CampignDetail(id:String)
        case ScreenShotList

        var value:String {
            switch self {
            case .CampignList:
              return  "CampignList"
            case .CampignDetail(let id):
                return id
            case .ScreenShotList :
                return "ScreenShotList"
            }

        }
    }

    func getBaseForCacheLocal(with fileName:String) -> String? {

        let filePath = FileManager.default.getDocumentPath(forItemName: self.LocalServiceCacheDownloadDir)
        if FileManager.default.directoryExists(atPath: filePath) {
            return filePath.stringByAppendingPathComponent(fileName)
        } else {
            if  FileManager.default.createDirectory(withFolderName: self.LocalServiceCacheDownloadDir) {
                return filePath.stringByAppendingPathComponent(fileName)
            }
        }
        return nil
    }



    //------------------------------------------------------------

    @discardableResult
    func cacheDataToLocal<T>(with Object:T,to key:WSCacheKeys) -> Bool {
        let success = NSKeyedArchiver.archiveRootObject(Object, toFile: getBaseForCacheLocal(with: key.value)!)
        if success {
            print( "Local Data Cached\(String(describing: getBaseForCacheLocal(with: key.value)))")
        } else {
            print("Error")
        }

        return success

    }

    //------------------------------------------------------------

    func loadCachedDataFromLocal<T>(with key:WSCacheKeys ) -> T? {
        return NSKeyedUnarchiver.unarchiveObject(withFile: getBaseForCacheLocal(with: key.value)!) as? T
    }


    //------------------------------------------------------------


    func removeAllCacheDirs () {
        do {
            try FileManager.default.removeItem(atPath: self.getBaseForCacheLocal(with: "")!)

        } catch {
            print("error in remove dir \(error.localizedDescription)")
        }

    }

    //--------------------------------------------------------------------------------


}

Here is some helper methods of extension FileManager

public var getDocumentDirectoryPath: String {
    let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    return documentDirectory
}

public func getDocumentPath(forItemName name: String)-> String {
    return getDocumentDirectoryPath.stringByAppendingPathComponent(name)
}

public func directoryExists(atPath filePath: String)-> Bool {
    var isDir = ObjCBool(true)
    return FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir )
}

public func createDirectory(withFolderName name: String)-> Bool {
    let finalPath = getDocumentDirectoryPath.stringByAppendingPathComponent(name)
    return createDirectory(atPath: finalPath)
}

Here Is String extension's method

public func stringByAppendingPathComponent(_ path: String) -> String {
    let fileUrl = URL.init(fileURLWithPath: self)
    let filePath = fileUrl.appendingPathComponent(path).path
    return filePath
}

How to use it ?

To save

   OfflineManager.sharedManager.cacheDataToLocal(with: object as! [String:Any], to: .CampignList)

To read data

    DispatchQueue.global().async {
        // GET OFFLINE DATA
        if let object:[String:Any] = OfflineManager.sharedManager.loadCachedDataFromLocal(with: .CampignList) {
            do {
                let data = try  JSONSerialization.data(withJSONObject: object, options: [])
                let object = try CampaignListResponse.init(data: data)
                self.arrCampignList = object.data ?? []
                DispatchQueue.main.async {
                    self.tableVIew.reloadData()
                }
            } catch {
            }
        }
      }

Note: You can define your own WSCacheKeys for type of your json like i am fetching some campaign list

Upvotes: 1

Imanou Petit
Imanou Petit

Reputation: 92409

#1. Save a Swift Array as a json file

The following Swift 3 / iOS 10 code shows how to transform an Array instance into json data and save it into a json file located in an iPhone's document directory using FileManager and JSONSerialization:

func saveToJsonFile() {
    // Get the url of Persons.json in document directory
    guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let fileUrl = documentDirectoryUrl.appendingPathComponent("Persons.json")

    let personArray =  [["person": ["name": "Dani", "age": "24"]], ["person": ["name": "ray", "age": "70"]]]

    // Transform array into data and save it into file
    do {
        let data = try JSONSerialization.data(withJSONObject: personArray, options: [])
        try data.write(to: fileUrl, options: [])
    } catch {
        print(error)
    }
}

/*
 Content of Persons.json file after operation:
 [{"person":{"name":"Dani","age":"24"}},{"person":{"name":"ray","age":"70"}}]
*/

As an alternative, you can implement the following code that use streams:

func saveToJsonFile() {
    // Get the url of Persons.json in document directory
    guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let fileUrl = documentDirectoryUrl.appendingPathComponent("Persons.json")

    let personArray =  [["person": ["name": "Dani", "age": "24"]], ["person": ["name": "ray", "age": "70"]]]

    // Create a write-only stream
    guard let stream = OutputStream(toFileAtPath: fileUrl.path, append: false) else { return }
    stream.open()
    defer {
        stream.close()
    }

    // Transform array into data and save it into file
    var error: NSError?
    JSONSerialization.writeJSONObject(personArray, to: stream, options: [], error: &error)

    // Handle error
    if let error = error {
        print(error)
    }
}

/*
 Content of Persons.json file after operation:
 [{"person":{"name":"Dani","age":"24"}},{"person":{"name":"ray","age":"70"}}]
*/

#2. Get a Swift Array from a json file

The following Swift 3 / iOS 10 code shows how to get data from a json file located in an iPhone's document directory and transform it into an Array instance using FileManager and JSONSerialization:

/*
 Content of Persons.json file:
 [{"person":{"name":"Dani","age":"24"}},{"person":{"name":"ray","age":"70"}}]
*/

func retrieveFromJsonFile() {
    // Get the url of Persons.json in document directory
    guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let fileUrl = documentsDirectoryUrl.appendingPathComponent("Persons.json")

    // Read data from .json file and transform data into an array
    do {
        let data = try Data(contentsOf: fileUrl, options: [])
        guard let personArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: [String: String]]] else { return }
        print(personArray) // prints [["person": ["name": "Dani", "age": "24"]], ["person": ["name": "ray", "age": "70"]]]
    } catch {
        print(error)
    }
}

As an alternative, you can implement the following code that use streams:

/*
 Content of Persons.json file:
 [{"person":{"name":"Dani","age":"24"}},{"person":{"name":"ray","age":"70"}}]
*/

func retrieveFromJsonFile() {
    // Get the url of Persons.json in document directory
    guard let documentsDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let fileUrl = documentsDirectoryUrl.appendingPathComponent("Persons.json")

    // Create a read-only stream
    guard let stream = InputStream(url: fileUrl) else { return }
    stream.open()
    defer {
        stream.close()
    }

    // Read data from .json file and transform data into an array
    do {
        guard let personArray = try JSONSerialization.jsonObject(with: stream, options: []) as? [[String: [String: String]]] else { return }
        print(personArray) // prints [["person": ["name": "Dani", "age": "24"]], ["person": ["name": "ray", "age": "70"]]]
    } catch {
        print(error)
    }
}

The Playground located in the Github's Save-and-read-JSON-from-Playground repo shows how to save a Swift Array into a json file and how to read a json file and get a Swift Array from it.

Upvotes: 33

Isuru
Isuru

Reputation: 31283

If you're like me who doesn't like to use a whole new third-party framework just for a trivial thing like this, here's my solution in vanilla Swift. From creating a .json file in the Documents folder to writing JSON in to it.

let documentsDirectoryPathString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
let documentsDirectoryPath = NSURL(string: documentsDirectoryPathString)!

let jsonFilePath = documentsDirectoryPath.URLByAppendingPathComponent("test.json")
let fileManager = NSFileManager.defaultManager()
var isDirectory: ObjCBool = false

// creating a .json file in the Documents folder
if !fileManager.fileExistsAtPath(jsonFilePath.absoluteString, isDirectory: &isDirectory) {
    let created = fileManager.createFileAtPath(jsonFilePath.absoluteString, contents: nil, attributes: nil)
    if created {
        print("File created ")
    } else {
        print("Couldn't create file for some reason")
    }
} else {
    print("File already exists")
}

// creating an array of test data
var numbers = [String]()
for var i = 0; i < 100; i++ {
    numbers.append("Test\(i)")
}

// creating JSON out of the above array
var jsonData: NSData!
do {
    jsonData = try NSJSONSerialization.dataWithJSONObject(numbers, options: NSJSONWritingOptions())
    let jsonString = String(data: jsonData, encoding: NSUTF8StringEncoding)
    print(jsonString)
} catch let error as NSError {
    print("Array to JSON conversion failed: \(error.localizedDescription)")
}

// Write that JSON to the file created earlier
let jsonFilePath = documentsDirectoryPath.URLByAppendingPathComponent("test.json")
do {
    let file = try NSFileHandle(forWritingToURL: jsonFilePath)
    file.writeData(jsonData)
    print("JSON data was written to teh file successfully!")
} catch let error as NSError {
    print("Couldn't write to file: \(error.localizedDescription)")
}

Upvotes: 43

Teemu Kurppa
Teemu Kurppa

Reputation: 4829

I recommend that you use SwiftyJSON framework. Study its documentation and in addition learn how to write strings to files (hint: NSFileHandle)

Something like the code below, but you really need to study both SwiftyJSON and NSFileHandle to learn how to both serialize JSON data to a file and parse JSON data from a file

let levels = ["unlocked", "locked", "locked"]
let json = JSON(levels)
let str = json.description
let data = str.dataUsingEncoding(NSUTF8StringEncoding)!
if let file = NSFileHandle(forWritingAtPath:path) {
    file.writeData(data)
} 

Upvotes: 16

Related Questions