245
votes

I'm really struggling with trying to read a JSON file into Swift so I can play around with it. I've spent the best part of 2 days re-searching and trying different methods but no luck as of yet so I have signed up to StackOverFlow to see if anyone can point me in the right direction.....

My JSON file is called test.json and contains the following:

{
  "person":[
     {
       "name": "Bob",
       "age": "16",
       "employed": "No"
     },
     {
       "name": "Vinny",
       "age": "56",
       "employed": "Yes"
     }
  ]
}    

The file is stored in the documents directly and I access it using the following code:

let file = "test.json"
let dirs : String[] = NSSearchPathForDirectoriesInDomains(
                                                          NSSearchpathDirectory.DocumentDirectory,
                                                          NSSearchPathDomainMask.AllDomainMask,
                                                          true) as String[]

if (dirs != nil) {
    let directories: String[] = dirs
    let dir = directories[0]
    let path = dir.stringByAppendingPathComponent(file)
}

var jsonData = NSData(contentsOfFile:path, options: nil, error: nil)
println("jsonData \(jsonData)" // This prints what looks to be JSON encoded data.

var jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: nil) as? NSDictionary

println("jsonDict \(jsonDict)") - This prints nil..... 

If anyone can just give me a push in the right direction on how I can de-serialize the JSON file and put it in an accessible Swift object I will be eternally grateful!

Kind Regards,

Krivvenz.

29
use the error parameter...Matthias Bauch
Please post actual, compilable code. As it is now, path is visible only in if scope, and unresolved when you use it in NSData(contentsOfFile, options, error); you also have typos in enum names.Kreiri
My API is fully updated for Swift 3: github.com/borchero/WebParsingborchero
this is key -> "values": "%LOAD VALUE FROM tmclass.json file%" and i need to parse another JSON from file then how i can achieve this in SWIFT ?Mayur Shinde

29 Answers

334
votes

Follow the below code :

if let path = NSBundle.mainBundle().pathForResource("test", ofType: "json")
{
    if let jsonData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)
    {
        if let jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary
        {
            if let persons : NSArray = jsonResult["person"] as? NSArray
            {
                // Do stuff
            }
        }
     }
}

The array "persons" will contain all data for key person. Iterate throughs to fetch it.

Swift 4.0:

if let path = Bundle.main.path(forResource: "test", ofType: "json") {
    do {
          let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
          let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
          if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let person = jsonResult["person"] as? [Any] {
                    // do stuff
          }
      } catch {
           // handle error
      }
}
149
votes

If anyone is looking for SwiftyJSON Answer:
Update:
For Swift 3/4:

if let path = Bundle.main.path(forResource: "assets/test", ofType: "json") {
    do {
        let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
        let jsonObj = try JSON(data: data)
        print("jsonData:\(jsonObj)")
    } catch let error {
        print("parse error: \(error.localizedDescription)")
    }
} else {
    print("Invalid filename/path.")
}
131
votes

Swift 4.x and 5.x using Decodable

struct ResponseData: Decodable {
    var person: [Person]
}
struct Person : Decodable {
    var name: String
    var age: String
    var employed: String
}

func loadJson(filename fileName: String) -> [Person]? {
    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            let jsonData = try decoder.decode(ResponseData.self, from: data)
            return jsonData.person
        } catch {
            print("error:\(error)")
        }
    }
    return nil
}

Swift 3

func loadJson(filename fileName: String) -> [String: AnyObject]? {
    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        do {
            let data = try Data(contentsOf: url)
            let object = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
            if let dictionary = object as? [String: AnyObject] {
                return dictionary
            }
        } catch {
            print("Error!! Unable to parse  \(fileName).json")
        }
    }
    return nil
}
29
votes

Swift 5.1, Xcode 11

You can use this:


struct Person : Codable {
    let name: String
    let lastName: String
    let age: Int
}

func loadJson(fileName: String) -> Person? {
   let decoder = JSONDecoder()
   guard
        let url = Bundle.main.url(forResource: fileName, withExtension: "json"),
        let data = try? Data(contentsOf: url),
        let person = try? decoder.decode(Person.self, from: data)
   else {
        return nil
   }

   return person
}
24
votes

Xcode 8 Swift 3 read json from file update:

    if let path = Bundle.main.path(forResource: "userDatabseFakeData", ofType: "json") {
        do {
            let jsonData = try NSData(contentsOfFile: path, options: NSData.ReadingOptions.mappedIfSafe)
            do {
                let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
                if let people : [NSDictionary] = jsonResult["person"] as? [NSDictionary] {
                    for person: NSDictionary in people {
                        for (name,value) in person {
                            print("\(name) , \(value)")
                        }
                    }
                }
            } catch {}
        } catch {}
    }
14
votes

Updated names for Swift 3.0

Based on Abhishek's answer and Druva's answer

func loadJson(forFilename fileName: String) -> NSDictionary? {

    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        if let data = NSData(contentsOf: url) {
            do {
                let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as? NSDictionary

                return dictionary
            } catch {
                print("Error!! Unable to parse  \(fileName).json")
            }
        }
        print("Error!! Unable to load  \(fileName).json")
    }

    return nil
}
14
votes

Simplifying the example provided by Peter Kreinz. Works with Swift 4.2.

The extension function:

extension Decodable {
  static func parse(jsonFile: String) -> Self? {
    guard let url = Bundle.main.url(forResource: jsonFile, withExtension: "json"),
          let data = try? Data(contentsOf: url),
          let output = try? JSONDecoder().decode(self, from: data)
        else {
      return nil
    }

    return output
  }
}

The example model:

struct Service: Decodable {
  let name: String
}

The example usage:

/// service.json
/// { "name": "Home & Garden" }

guard let output = Service.parse(jsonFile: "service") else {
// do something if parsing failed
 return
}

// use output if all good

The example will work with arrays, too:

/// services.json
/// [ { "name": "Home & Garden" } ]

guard let output = [Service].parse(jsonFile: "services") else {
// do something if parsing failed
 return
}

// use output if all good

Notice how we don't provide any unnecessary generics, thus we don't need to cast the result of parse.

10
votes

Swift 2.1 answer (based on Abhishek's) :

    if let path = NSBundle.mainBundle().pathForResource("test", ofType: "json") {
        do {
            let jsonData = try NSData(contentsOfFile: path, options: NSDataReadingOptions.DataReadingMappedIfSafe)
            do {
                let jsonResult: NSDictionary = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
                if let people : [NSDictionary] = jsonResult["person"] as? [NSDictionary] {
                    for person: NSDictionary in people {
                        for (name,value) in person {
                            print("\(name) , \(value)")
                        }
                    }
                }
            } catch {}
        } catch {}
    }
10
votes

Swift 3.0, Xcode 8, iOS 10

 if let path = Bundle.main.url(forResource: "person", withExtension: "json") {

        do {
            let jsonData = try Data(contentsOf: path, options: .mappedIfSafe)
            do {
                if let jsonResult = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions(rawValue: 0)) as? NSDictionary {
                    if let personArray = jsonResult.value(forKey: "person") as? NSArray {
                        for (_, element) in personArray.enumerated() {
                            if let element = element as? NSDictionary {
                                let name = element.value(forKey: "name") as! String
                                let age = element.value(forKey: "age") as! String
                                let employed = element.value(forKey: "employed") as! String
                                print("Name: \(name),  age: \(age), employed: \(employed)")
                            }
                        }
                    }
                }
            } catch let error as NSError {
                print("Error: \(error)")
            }
        } catch let error as NSError {
            print("Error: \(error)")
        }
    }

Output:

Name: Bob,  age: 16, employed: No
Name: Vinny,  age: 56, employed: Yes
7
votes

This worked great with me

func readjson(fileName: String) -> NSData{

    let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "json")
    let jsonData = NSData(contentsOfMappedFile: path!)

    return jsonData!
}
7
votes

Here is my solution using SwiftyJSON

if let path : String = NSBundle.mainBundle().pathForResource("filename", ofType: "json") {
    if let data = NSData(contentsOfFile: path) {

        let json = JSON(data: data)

    }
}
7
votes
fileprivate class BundleTargetingClass {}
func loadJSON<T>(name: String) -> T? {
  guard let filePath = Bundle(for: BundleTargetingClass.self).url(forResource: name, withExtension: "json") else {
    return nil
  }

  guard let jsonData = try? Data(contentsOf: filePath, options: .mappedIfSafe) else {
    return nil
  }

  guard let json = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) else {
    return nil
  }

  return json as? T
}

👆🏻 copy-paste ready, 3rd party framework independent solution.

usage 👇🏻

let json:[[String : AnyObject]] = loadJSON(name: "Stations")!

7
votes

Swift 4: Try out my solution:

test.json

{
    "person":[
        {
            "name": "Bob",
            "age": "16",
            "employed": "No"
        },
        {
            "name": "Vinny",
            "age": "56",
            "employed": "Yes"
        }
    ]
}

RequestCodable.swift

import Foundation

struct RequestCodable:Codable {
    let person:[PersonCodable]
}

PersonCodable.swift

import Foundation

struct PersonCodable:Codable {
    let name:String
    let age:String
    let employed:String
}

Decodable+FromJSON.swift

import Foundation

extension Decodable {

    static func fromJSON<T:Decodable>(_ fileName: String, fileExtension: String="json", bundle: Bundle = .main) throws -> T {
        guard let url = bundle.url(forResource: fileName, withExtension: fileExtension) else {
            throw NSError(domain: NSURLErrorDomain, code: NSURLErrorResourceUnavailable)
        }

        let data = try Data(contentsOf: url)

        return try JSONDecoder().decode(T.self, from: data)
    }
}

Example:

let result = RequestCodable.fromJSON("test") as RequestCodable?

result?.person.compactMap({ print($0) }) 

/*
PersonCodable(name: "Bob", age: "16", employed: "No")
PersonCodable(name: "Vinny", age: "56", employed: "Yes")
*/
5
votes

I'm providing another answer because none of the ones here are geared toward loading the resource from the test bundle. If you are consuming a remote service that puts out JSON and want to unit test parsing the results without hitting the actual service, you take one or more responses and put them into files in the Tests folder in your project.

func testCanReadTestJSONFile() {
    let path = NSBundle(forClass: ForecastIOAdapterTests.self).pathForResource("ForecastIOSample", ofType: "json")
    if let jsonData = NSData(contentsOfFile:path!) {
        let json = JSON(data: jsonData)
        if let currentTemperature = json["currently"]["temperature"].double {
            println("json: \(json)")
            XCTAssertGreaterThan(currentTemperature, 0)
        }
    }
}

This also uses SwiftyJSON but the core logic of getting the test bundle and loading the file is the answer to the question.

5
votes

The following code works for me. I am using Swift 5

let path = Bundle.main.path(forResource: "yourJSONfileName", ofType: "json")
var jsonData = try! String(contentsOfFile: path!).data(using: .utf8)!

Then, if your Person Struct (or Class) is Decodable (and also all of its properties), you can simply do:

let person = try! JSONDecoder().decode(Person.self, from: jsonData)

I avoided all the error handling code to make the code more legible.

5
votes

Use this generic function

func readJSONFromFile<T: Decodable>(fileName: String, type: T.Type) -> T? {
    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            let jsonData = try decoder.decode(T.self, from: data)
            return jsonData
        } catch {
            print("error:\(error)")
        }
    }
    return nil
}

with this line of code:

let model = readJSONFromFile(fileName: "Model", type: Model.self)

for this type:

struct Model: Codable {
    let tall: Int
}
3
votes

Latest swift 3.0 absolutely working

func loadJson(filename fileName: String) -> [String: AnyObject]?
{
    if let url = Bundle.main.url(forResource: fileName, withExtension: "json") 
{
      if let data = NSData(contentsOf: url) {
          do {
                    let object = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
                    if let dictionary = object as? [String: AnyObject] {
                        return dictionary
                    }
                } catch {
                    print("Error!! Unable to parse  \(fileName).json")
                }
            }
            print("Error!! Unable to load  \(fileName).json")
        }
        return nil
    }
3
votes

Updated for Swift 3 with safest way

    private func readLocalJsonFile() {

    if let urlPath = Bundle.main.url(forResource: "test", withExtension: "json") {

        do {
            let jsonData = try Data(contentsOf: urlPath, options: .mappedIfSafe)

            if let jsonDict = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as? [String: AnyObject] {

                if let personArray = jsonDict["person"] as? [[String: AnyObject]] {

                    for personDict in personArray {

                        for (key, value) in personDict {

                            print(key, value)
                        }
                        print("\n")
                    }
                }
            }
        }

        catch let jsonError {
            print(jsonError)
        }
    }
}

enter image description here

3
votes

Swift 4 JSON to Class with Decodable - for those who prefer classes

Define the classes as follows:

class People: Decodable {
  var person: [Person]?

  init(fileName : String){
    // url, data and jsonData should not be nil
    guard let url = Bundle.main.url(forResource: fileName, withExtension: "json") else { return }
    guard let data = try? Data(contentsOf: url) else { return }
    guard let jsonData = try? JSONDecoder().decode(People.self, from: data) else { return }

    // assigns the value to [person]
    person = jsonData.person
  }
}

class Person : Decodable {
  var name: String
  var age: String
  var employed: String
}

Usage, pretty abstract:

let people = People(fileName: "people")
let personArray = people.person

This allow methods for both People and Person classes, variables (attributes) and methods can also marked as private if needed.

1
votes

Based on Abhishek's answer, for iOS 8 this would be:

let masterDataUrl: NSURL = NSBundle.mainBundle().URLForResource("masterdata", withExtension: "json")!
let jsonData: NSData = NSData(contentsOfURL: masterDataUrl)!
let jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: nil) as! NSDictionary
var persons : NSArray = jsonResult["person"] as! NSArray
1
votes

This worked for me with XCode 8.3.3

func fetchPersons(){

    if let pathURL = Bundle.main.url(forResource: "Person", withExtension: "json"){

        do {

            let jsonData = try Data(contentsOf: pathURL, options: .mappedIfSafe)

            let jsonResult = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) as! [String: Any]
            if let persons = jsonResult["person"] as? [Any]{

                print(persons)
            }

        }catch(let error){
            print (error.localizedDescription)
        }
    }
}
1
votes

Swift 4.1 Updated Xcode 9.2

if let filePath = Bundle.main.path(forResource: "fileName", ofType: "json"), let data = NSData(contentsOfFile: filePath) {

     do {
      let json = try JSONSerialization.jsonObject(with: data as Data, options: JSONSerialization.ReadingOptions.allowFragments)        
        }
     catch {
                //Handle error
           }
 }
1
votes
//change type based on your struct and right JSON file

let quoteData: [DataType] =
    load("file.json")

func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}


0
votes

I’ve used below code to fetch JSON from FAQ-data.json file present in project directory .

I’m implementing in Xcode 7.3 using Swift.

     func fetchJSONContent() {
            if let path = NSBundle.mainBundle().pathForResource("FAQ-data", ofType: "json") {

                if let jsonData = NSData(contentsOfFile: path) {
                    do {
                        if let jsonResult: NSDictionary = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary {

                            if let responseParameter : NSDictionary = jsonResult["responseParameter"] as? NSDictionary {

                                if let response : NSArray = responseParameter["FAQ"] as? NSArray {
                                    responseFAQ = response
                                    print("response FAQ : \(response)")
                                }
                            }
                        }
                    }
                    catch { print("Error while parsing: \(error)") }
                }
            }
        }

override func viewWillAppear(animated: Bool) {
        fetchFAQContent()
    }

Structure of JSON file :

{
    "status": "00",
    "msg": "FAQ List ",
    "responseParameter": {
        "FAQ": [
            {                
                "question": “Question No.1 here”,
                "answer": “Answer goes here”,  
                "id": 1
            },
            {                
                "question": “Question No.2 here”,
                "answer": “Answer goes here”,
                "id": 2
            }
            . . .
        ]
    }
}
0
votes

I might also recommend Ray Wenderlich's Swift JSON Tutorial (which also covers the awesome SwiftyJSON alternative, Gloss). An excerpt (which granted, by itself, does not fully answer the poster, but the added value of this answer is the link, so no -1's for that, please):

In Objective-C, parsing and deserializing JSON is fairly straightforward:

NSArray *json = [NSJSONSerialization JSONObjectWithData:JSONData
options:kNilOptions error:nil];
NSString *age = json[0][@"person"][@"age"];
NSLog(@"Dani's age is %@", age);

In Swift, parsing and deserializing JSON is a little more tedious due to Swift optionals and type-safety [but as] part of Swift 2.0 the guard statement was introduced to help get rid of nested if statements:

var json: Array!
do {
  json = try NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions()) as? Array
} catch {
  print(error)
}

guard let item = json[0] as? [String: AnyObject],
  let person = item["person"] as? [String: AnyObject],
  let age = person["age"] as? Int else {
    return;
}
print("Dani's age is \(age)")

Of course, in XCode 8.x, you just double-tap the space bar and say "Hey, Siri, please deserialize this JSON for me in Swift 3.0 with space/tab-indents."

0
votes

SWIFTYJSON VERSION SWIFT 3

func loadJson(fileName: String) -> JSON {

    var dataPath:JSON!

    if let path : String = Bundle.main.path(forResource: fileName, ofType: "json") {
        if let data = NSData(contentsOfFile: path) {
             dataPath = JSON(data: data as Data)
        }
    }
    return dataPath
}
0
votes

First create a Struc codable like this:

  struct JuzgadosList : Codable {
    var CP : Int
    var TEL : String
    var LOCAL : String
    var ORGANO : String
    var DIR : String
}

Now declare the variable

 var jzdosList = [JuzgadosList]()

Read from main directory

func getJsonFromDirectory() {

        if let path = Bundle.main.path(forResource: "juzgados", ofType: "json") {
            do {
                let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
                let jList = try JSONDecoder().decode([JuzgadosList].self, from: data)
                self.jzdosList = jList

                DispatchQueue.main.async() { () -> Void in
                    self.tableView.reloadData()
                }

            } catch let error {
                print("parse error: \(error.localizedDescription)")
            }
        } else {
            print("Invalid filename/path.")
        }
    }

Read from web

func getJsonFromUrl(){

        self.jzdosList.removeAll(keepingCapacity: false)

        print("Internet Connection Available!")

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

        let request = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
        URLSession.shared.dataTask(with: request) { (data, response, err) in
            guard let data = data else { return }
            do {
                let jList = try JSONDecoder().decode([JuzgadosList].self, from: data)
                self.jzdosList = jList

                DispatchQueue.main.async() { () -> Void in
                    self.tableView.reloadData()
                }
            } catch let jsonErr {
                print("Error serializing json:", jsonErr)
            }
        }.resume()
    }
0
votes

After cleaning and polishing my code, I came to these two functions that you could just add to your project and use them very neat and fast to read from a json file and convert the data to whatever type you want it to be!

public func readDataRepresentationFromFile(resource: String, type: String) -> Data? {
    let filePath = Bundle.main.path(forResource: resource, ofType: type)
    
    if let path = filePath {
        let result = FileManager.default.contents(atPath: path)
        return result
    }
    return nil
}

and then with the help of this function you can convert your data to whatever type you want:

public func getObject<T: Codable>(of type: T.Type, from file: String) -> T?  {
    guard let data = readDataRepresentationFromFile(resource: file, type: "json") else {
        return nil
    }
    if let object = try? JSONDecoder().decode(type, from: data) {
        return object
    }
    return nil
}

Example of application of this code: call this functions wherever in your code, give it the name of your json file and thats all you need!

func getInputDataFromSomeJson(jsonFileName: String) -> YourReqiuredOutputType? {
    return getObject(of: YourReqiuredOutputType.self, from: jsonFileName)
}
0
votes

Swift 5+

Decoding the jsonData with your Struct

if let jsonData = readFile(forName: <your file name>) {

do {
                let decodedData = try JSONDecoder().decode(<your struct name>.self, from: jsonData)
                return decodedData.<what you expect>
            } catch { print("JSON decode error") }
}

This will read the file and return jsonData

In case you are actually in another bundle (test for instance), use:
let bundlePath = Bundle(for: type(of: self)).path(forResource: name, ofType: "json")

private func readFile(forName name: String) -> Data? {
        do {

            if let bundlePath = Bundle.main.path(forResource: name, ofType: "json"),
                let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
                return jsonData
            }
        } catch {
            print(error)
        }
        return nil
    }