109

I have been searching for days to convert a fairly simple JSON string to an object type in Swift but with no avail.

Here is the code for web service call:

func GetAllBusiness() {

        Alamofire.request(.GET, "http://MyWebService/").responseString { (request, response, string, error) in

                println(string)

        }
}

I have a swift struct Business.swift:

struct Business {
    var Id : Int = 0
    var Name = ""
    var Latitude = ""
    var Longitude = ""
    var Address = ""
}

Here is my test service deployed:

[
  {
    "Id": 1,
    "Name": "A",
    "Latitude": "-35.243256",
    "Longitude": "149.110701",
    "Address": null
  },
  {
    "Id": 2,
    "Name": "B",
    "Latitude": "-35.240592",
    "Longitude": "149.104843",
    "Address": null
  }
  ...
]

It would be a delight if someone guide me through this.

Thanks.

swiftBoy
  • 35,607
  • 26
  • 136
  • 135
Hasan Nizamani
  • 1,093
  • 2
  • 7
  • 6

17 Answers17

71

for swift 3/4

extension String {
    func toJSON() -> Any? {
        guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
        return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
    }
}

Example Usage:

 let dict = myString.toJSON() as? [String:AnyObject] // can be any type here
Just a coder
  • 15,480
  • 16
  • 85
  • 138
  • 2
    Future footnote: instead of `do-catch`, `try?` can be used here, which will give the same result as returning nil in `catch`. – Okhan Okbay Jun 27 '17 at 21:11
  • 1
    ... and how exactly one accesses a parameter after this conversion? – Starwave Mar 07 '18 at 17:14
  • 1
    For anyone else interested: let jsonObjectAsNSDictionary = responseString?.toJSON() as! [String:AnyObject] print(jsonObjectAsNSDictionary["permissions"]!["canaddeditowncomment"]) – Starwave Mar 07 '18 at 18:07
  • 1
    Please correct swift syntax ... `extension String { func toJSON() -> Any? { guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil } return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) } }` – Yasir Ali Jan 04 '19 at 11:00
64

Here are some tips how to begin with simple example.

Consider you have following JSON Array String (similar to yours) like:

 var list:Array<Business> = []

  // left only 2 fields for demo
  struct Business {
    var id : Int = 0
    var name = ""               
 }

 var jsonStringAsArray = "[\n" +
        "{\n" +
        "\"id\":72,\n" +
        "\"name\":\"Batata Cremosa\",\n" +            
        "},\n" +
        "{\n" +
        "\"id\":183,\n" +
        "\"name\":\"Caldeirada de Peixes\",\n" +            
        "},\n" +
        "{\n" +
        "\"id\":76,\n" +
        "\"name\":\"Batata com Cebola e Ervas\",\n" +            
        "},\n" +
        "{\n" +
        "\"id\":56,\n" +
        "\"name\":\"Arroz de forma\",\n" +            
    "}]"


        // convert String to NSData
        var data: NSData = jsonStringAsArray.dataUsingEncoding(NSUTF8StringEncoding)!
        var error: NSError?

        // convert NSData to 'AnyObject'
        let anyObj: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0),
            error: &error)
        println("Error: \(error)")

     // convert 'AnyObject' to Array<Business>
     list = self.parseJson(anyObj!)

     //===============

    func parseJson(anyObj:AnyObject) -> Array<Business>{

        var list:Array<Business> = []

         if  anyObj is Array<AnyObject> {

            var b:Business = Business()

            for json in anyObj as Array<AnyObject>{
             b.name = (json["name"] as AnyObject? as? String) ?? "" // to get rid of null
             b.id  =  (json["id"]  as AnyObject? as? Int) ?? 0                 

               list.append(b)
            }// for

        } // if

      return list

    }//func    

[EDIT]

To get rid of null changed to:

b.name = (json["name"] as AnyObject? as? String) ?? ""
b.id  =  (json["id"]  as AnyObject? as? Int) ?? 0 

See also Reference of Coalescing Operator (aka ??)

Hope it will help you to sort things out,

Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
  • Awesome! Worked like a charm. Thanks! Just one tiny thing, it is giving an error if the element in JSON is null. Like in: b.name = json["name"] as AnyObject! as String If name is null, how can I add condition to make it nullable? – Hasan Nizamani Sep 02 '14 at 12:24
  • what is the need to cast to AnyObject before cast to String? – Bateramos Feb 04 '15 at 15:29
  • @Bateramos nothing. You get optional `AnyObject` by key just be sure before downcasting to String that its not `nil`. For that reason I you can to incapsualte by using `!` or in my case `?` with holdplace `??` – Maxim Shoustin Feb 04 '15 at 16:37
  • I guess, If you create object `var b:Business = Business()` out side loop, then it might show same data in each list element. – Patriks Jul 20 '15 at 14:28
  • Simple and clean way =)))) – Henadzi Rabkin Feb 15 '21 at 16:00
28

As simple String extension should suffice:

extension String {

    var parseJSONString: AnyObject? {

        let data = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        if let jsonData = data {
            // Will return an object or nil if JSON decoding fails
            return NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil)
        } else {
            // Lossless conversion of the string was not possible
            return nil
        }
    }
}

Then:

var jsonString = "[\n" +
    "{\n" +
    "\"id\":72,\n" +
    "\"name\":\"Batata Cremosa\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":183,\n" +
    "\"name\":\"Caldeirada de Peixes\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":76,\n" +
    "\"name\":\"Batata com Cebola e Ervas\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":56,\n" +
    "\"name\":\"Arroz de forma\",\n" +            
"}]"

let json: AnyObject? = jsonString.parseJSONString
println("Parsed JSON: \(json!)")
println("json[3]: \(json![3])")

/* Output:

Parsed JSON: (
    {
    id = 72;
    name = "Batata Cremosa";
    },
    {
    id = 183;
    name = "Caldeirada de Peixes";
    },
    {
    id = 76;
    name = "Batata com Cebola e Ervas";
    },
    {
    id = 56;
    name = "Arroz de forma";
    }
)

json[3]: {
    id = 56;
    name = "Arroz de forma";
}
*/
PassKit
  • 12,231
  • 5
  • 57
  • 75
20

Swift 4 parses JSON much more elegantly. Just adopt the codable protocol for your structure as per this simplified example:

struct Business: Codable {
    let id: Int
    let name: String
}

To parse the JSON array, you tell the decoder what the objects of the data array are

let parsedData = decoder.decode([Business].self, from: data)

Here's a full working example:

import Foundation

struct Business: Codable {
    let id: Int
    let name: String
}

// Generating the example JSON data: 
let originalObjects = [Business(id: 0, name: "A"), Business(id: 1, name: "B")]
let encoder = JSONEncoder()
let data = try! encoder.encode(originalObjects)

// Parsing the data: 
let decoder = JSONDecoder()
let parsedData = try! decoder.decode([Business].self, from: data)

For more background, check out this excellent guide.

Noyer282
  • 934
  • 7
  • 18
  • 1
    The downside for this is that one has to keep track of the struct, and if you have ~30 or more parameters, managing will become a big hassle. – Starwave Mar 07 '18 at 17:06
  • @Starwave: Not sure this addresses your concern, but note that you only need to include the fields you care about in the struct you use to decode the data. E.g. if the JSON was of the form [{"id": 1, "name: "A", "location": "Warsaw"},] you could still decode it using the same Business struct. The location field would simply be ignored. – Noyer282 Mar 08 '18 at 07:35
  • Bloody hell, I didn't thought of that... But then, what is the parsedData here? NSDictionary? – Starwave Mar 08 '18 at 13:01
  • @Starwave: It's an array of structs, just as in the original question. – Noyer282 Mar 11 '18 at 14:22
11

For Swift 4

I used @Passkit's logic but i had to update as per Swift 4


Step.1 Created extension for String Class

import UIKit


extension String
    {
        var parseJSONString: AnyObject?
        {
            let data = self.data(using: String.Encoding.utf8, allowLossyConversion: false)

            if let jsonData = data
            {
                // Will return an object or nil if JSON decoding fails
                do
                {
                    let message = try JSONSerialization.jsonObject(with: jsonData, options:.mutableContainers)
                    if let jsonResult = message as? NSMutableArray
                    {
                        print(jsonResult)

                        return jsonResult //Will return the json array output
                    }
                    else
                    {
                        return nil
                    }
                }
                catch let error as NSError
                {
                    print("An error occurred: \(error)")
                    return nil
                }
            }
            else
            {
                // Lossless conversion of the string was not possible
                return nil
            }
        }
    }

Step.2 This is how I used in my view controller

var jsonString = "[\n" +
    "{\n" +
    "\"id\":72,\n" +
    "\"name\":\"Batata Cremosa\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":183,\n" +
    "\"name\":\"Caldeirada de Peixes\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":76,\n" +
    "\"name\":\"Batata com Cebola e Ervas\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":56,\n" +
    "\"name\":\"Arroz de forma\",\n" +            
"}]"

 //Convert jsonString to jsonArray

let json: AnyObject? = jsonString.parseJSONString
print("Parsed JSON: \(json!)")
print("json[2]: \(json![2])")

All credit goes to original user, I just updated for latest swift version

Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
swiftBoy
  • 35,607
  • 26
  • 136
  • 135
9

For Swift 4, i wrote this extension using the Codable protocol:

struct Business: Codable {
    var id: Int
    var name: String
}

extension String {

    func parse<D>(to type: D.Type) -> D? where D: Decodable {

        let data: Data = self.data(using: .utf8)!

        let decoder = JSONDecoder()

        do {
            let _object = try decoder.decode(type, from: data)
            return _object

        } catch {
            return nil
        }
    }
}

var jsonString = "[\n" +
    "{\n" +
    "\"id\":72,\n" +
    "\"name\":\"Batata Cremosa\",\n" +
    "},\n" +
    "{\n" +
    "\"id\":183,\n" +
    "\"name\":\"Caldeirada de Peixes\",\n" +
    "},\n" +
    "{\n" +
    "\"id\":76,\n" +
    "\"name\":\"Batata com Cebola e Ervas\",\n" +
    "},\n" +
    "{\n" +
    "\"id\":56,\n" +
    "\"name\":\"Arroz de forma\",\n" +
"}]"

let businesses = jsonString.parse(to: [Business].self)
Danny Narváez
  • 576
  • 5
  • 10
7

I wrote a library which makes working with json data and deserialization a breeze in Swift. You can get it here: https://github.com/isair/JSONHelper

Edit: I updated my library, you can now do it with just this:

class Business: Deserializable {
    var id: Int?
    var name = "N/A"  // This one has a default value.

    required init(data: [String: AnyObject]) {
        id <-- data["id"]
        name <-- data["name"]
    }
}

var businesses: [Business]()

Alamofire.request(.GET, "http://MyWebService/").responseString { (request, response, string, error) in
    businesses <-- string
}

Old Answer:

First, instead of using .responseString, use .response to get a response object. Then change your code to:

func getAllBusinesses() {

    Alamofire.request(.GET, "http://MyWebService/").response { (request, response, data, error) in
        var businesses: [Business]?

        businesses <-- data

        if businesses == nil {
            // Data was not structured as expected and deserialization failed, do something.
        } else {
            // Do something with your businesses array. 
        }
    }
}

And you need to make a Business class like this:

class Business: Deserializable {
    var id: Int?
    var name = "N/A"  // This one has a default value.

    required init(data: [String: AnyObject]) {
        id <-- data["id"]
        name <-- data["name"]
    }
}

You can find the full documentation on my GitHub repo. Have fun!

isair
  • 1,790
  • 15
  • 15
5

For iOS 10 & Swift 3, using Alamofire & Gloss:

Alamofire.request("http://localhost:8080/category/en").responseJSON { response in

if let data = response.data {

    if let categories = [Category].from(data: response.data) {

        self.categories = categories

        self.categoryCollectionView.reloadData()
    } else {

        print("Casting error")
    }
  } else {

    print("Data is null")
  }
}

and here is the Category class

import Gloss

struct Category: Decodable {

    let categoryId: Int?
    let name: String?
    let image: String?

    init?(json: JSON) {
        self.categoryId = "categoryId" <~~ json
        self.name = "name" <~~ json
        self.image = "image" <~~ json
    }
}

IMO, this is by far the most elegant solution.

Timeless
  • 7,338
  • 9
  • 60
  • 94
  • 1
    replace Gloss with Codable since Gloss is archived ( in favor of Swift's Codable framework) – Mia Nov 30 '21 at 09:45
4

SWIFT4 - Easy and elegant way of decoding JSON strings to Struct.

First step - encode String to Data with .utf8 encoding.

Than decode your Data to YourDataStruct.

struct YourDataStruct: Codable {

let type, id: String

init(_ json: String, using encoding: String.Encoding = .utf8) throws {
    guard let data = json.data(using: encoding) else {
        throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
    }
    try self.init(data: data)
}

init(data: Data) throws {
    self = try JSONDecoder().decode(YourDataStruct.self, from: data)
}                                                                      
}

do { let successResponse = try WSDeleteDialogsResponse(response) }
} catch {}
Vitya Shurapov
  • 2,200
  • 2
  • 27
  • 32
3
let jsonString = "{\"id\":123,\"Name\":\"Munish\"}"

Convert String to NSData

 var data: NSData =jsonString.dataUsingEncoding(NSUTF8StringEncoding)!

 var error: NSError?

Convert NSData to AnyObject

var jsonObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data,     options: NSJSONReadingOptions.allZeros, error: &error)

println("Error: \\(error)")

let id = (jsonObject as! NSDictionary)["id"] as! Int

let name = (jsonObject as! NSDictionary)["name"] as! String

println("Id: \\(id)")

println("Name: \\(name)")
Munish Kapoor
  • 3,141
  • 2
  • 26
  • 41
3

It might be help someone. Similar example.

This is our Codable class to bind data. You can easily create this class using SwiftyJsonAccelerator

 class ModelPushNotificationFilesFile: Codable {

  enum CodingKeys: String, CodingKey {
    case url
    case id
    case fileExtension = "file_extension"
    case name
  }

  var url: String?
  var id: Int?
  var fileExtension: String?
  var name: String?

  init (url: String?, id: Int?, fileExtension: String?, name: String?) {
    self.url = url
    self.id = id
    self.fileExtension = fileExtension
    self.name = name
  }

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    url = try container.decodeIfPresent(String.self, forKey: .url)
    id = try container.decodeIfPresent(Int.self, forKey: .id)
    fileExtension = try container.decodeIfPresent(String.self, forKey: .fileExtension)
    name = try container.decodeIfPresent(String.self, forKey: .name)
  }

}

This is Json String

    let jsonString = "[{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/tulips.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/arctichare.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/serrano.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/peppers.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/pool.png\"}]"

Here we convert to swift object.

   let jsonData = Data(jsonString.utf8)

        let decoder = JSONDecoder()

        do {
            let fileArray = try decoder.decode([ModelPushNotificationFilesFile].self, from: jsonData)
            print(fileArray)
            print(fileArray[0].url)
        } catch {
            print(error.localizedDescription)
        }
Shourob Datta
  • 1,886
  • 22
  • 30
2

I like RDC's response, but why limit the JSON returned to have only arrays at the top level? I needed to allow a dictionary at the top level, so I modified it thus:

extension String
{
    var parseJSONString: AnyObject?
    {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        if let jsonData = data
        {
            // Will return an object or nil if JSON decoding fails
            do
            {
                let message = try NSJSONSerialization.JSONObjectWithData(jsonData, options:.MutableContainers)
                if let jsonResult = message as? NSMutableArray {
                    return jsonResult //Will return the json array output
                } else if let jsonResult = message as? NSMutableDictionary {
                    return jsonResult //Will return the json dictionary output
                } else {
                    return nil
                }
            }
            catch let error as NSError
            {
                print("An error occurred: \(error)")
                return nil
            }
        }
        else
        {
            // Lossless conversion of the string was not possible
            return nil
        }
    }
MR_22
  • 39
  • 1
  • 5
2

Wrap the json in the multiline string literal and try to parse like this:

import Foundation

// MARK: - DemoJSON
struct DemoJSON: Codable {
    let menu: Menu
}

// MARK: - Menu
struct Menu: Codable {
    let id, value: String
    let popup: Popup
}

// MARK: - Popup
struct Popup: Codable {
    let menuitem: [Menuitem]
}

// MARK: - Menuitem
struct Menuitem: Codable {
    let value, onclick: String
}



/// Make Network Request
typealias RequestCompletionHandler<T: Decodable> = (_ value: T?, _ error: Error?) -> Void


func callAPI<T: Decodable>(completionHandler: RequestCompletionHandler<T>) {
    let data = """
{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}
""".data(using: .utf8)!
    do {
        let value = try JSONDecoder().decode(T.self, from: data)
        completionHandler(value, nil)
    } catch {
        completionHandler(nil, error)
    }
}

callAPI { (model: DemoJSON?, error) in
    if let demoModel = model {
        print("Success: \(demoModel)")
    } else if let error = error {
        print("Error: \(error)")
    }
}
RTXGamer
  • 3,215
  • 6
  • 20
  • 29
1

You can use swift.quicktype.io for converting JSON to either struct or class. Even you can mention version of swift to genrate code.

Example JSON:

{
  "message": "Hello, World!"
}

Generated code:

import Foundation

typealias Sample = OtherSample

struct OtherSample: Codable {
    let message: String
}

// Serialization extensions

extension OtherSample {
    static func from(json: String, using encoding: String.Encoding = .utf8) -> OtherSample? {
        guard let data = json.data(using: encoding) else { return nil }
        return OtherSample.from(data: data)
    }

    static func from(data: Data) -> OtherSample? {
        let decoder = JSONDecoder()
        return try? decoder.decode(OtherSample.self, from: data)
    }

    var jsonData: Data? {
        let encoder = JSONEncoder()
        return try? encoder.encode(self)
    }

    var jsonString: String? {
        guard let data = self.jsonData else { return nil }
        return String(data: data, encoding: .utf8)
    }
}

extension OtherSample {
    enum CodingKeys: String, CodingKey {
        case message
    }
}
Rugmangathan
  • 3,186
  • 6
  • 33
  • 44
1

Using SwiftyJSON library, you could make it like

if let path : String = Bundle.main.path(forResource: "tiles", ofType: "json") {
    if let data = NSData(contentsOfFile: path) {
        let optData = try? JSON(data: data as Data)
        guard let json = optData else {
            return
        }
        for (_, object) in json {
            let name = object["name"].stringValue
            print(name)
        }
    }
} 
Mohamed Saleh
  • 2,881
  • 1
  • 23
  • 35
0

Here's a sample for you to make things simpler and easier. My String data in my database is a JSON file that looks like this:

[{"stype":"noun","sdsc":"careless disregard for consequences","swds":"disregard, freedom, impulse, licentiousness, recklessness, spontaneity, thoughtlessness, uninhibitedness, unrestraint, wantonness, wildness","anwds":"restraint, self-restraint"},{"stype":"verb","sdsc":"leave behind, relinquish","swds":"abdicate, back out, bail out, bow out, chicken out, cop out, cut loose, desert, discard, discontinue, ditch, drop, drop out, duck, dump, dust, flake out, fly the coop, give up the ship, kiss goodbye, leave, leg it, let go, opt out, pull out, quit, run out on, screw, ship out, stop, storm out, surrender, take a powder, take a walk, throw over, vacate, walk out on, wash hands of, withdraw, yield","anwds":"adopt, advance, allow, assert, begin, cherish, come, continue, defend, favor, go, hold, keep, maintain, persevere, pursue, remain, retain, start, stay, support, uphold"},{"stype":"verb","sdsc":"leave in troubled state","swds":"back out, desert, disown, forsake, jilt, leave, leave behind, quit, reject, renounce, throw over, walk out on","anwds":"adopt, allow, approve, assert, cherish, come, continue, defend, favor, keep, pursue, retain, stay, support, uphold"}]

To load this JSON String Data, follow these simple Steps. First, create a class for my MoreData Object like this:

class  MoreData {
public private(set) var stype : String
public private(set) var sdsc : String
public private(set) var swds : String
public private(set) var anwds : String

init( stype : String, sdsc : String, swds : String, anwds : String) {

    self.stype = stype
    self.sdsc = sdsc
    self.swds = swds
    self.anwds = anwds
}}

Second, create my String extension for my JSON String like this:

extension  String {
func toJSON() -> Any? {
    guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
    return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
}}

Third, create My Srevices Class to handle my String Data like this:

class Services {
static let instance: Services = Services()

func loadMoreDataByString(byString: String) -> [MoreData]{
    var  myVariable = [MoreData]()

    guard let ListOf = byString.toJSON() as? [[String: AnyObject]] else { return  [] }

    for object in ListOf {
        let stype  = object["stype"] as? String ?? ""
        let sdsc  = object["sdsc"] as? String ?? ""
         let swds  = object["swds"] as? String ?? ""
        let anwds  = object["anwds"] as? String ?? ""

        let myMoreData = MoreData(stype : stype, sdsc : sdsc, swds : swds, anwds : anwds)
        myVariable.append(myMoreData)
    }
    return myVariable
}}

Finally, call this Function from the View Controller to load data in the table view like this:

    func handlingJsonStringData(){
    moreData.removeAll(keepingCapacity: false)
    moreData =  Services.instance.loadMoreDataByString(byString: jsonString)
    print(self.moreData.count)
    tableView.reloadData()
}
-1

Use swiftyJson swiftyJson

platform :ios, '8.0'
use_frameworks!

target 'MyApp' do
pod 'SwiftyJSON', '~> 4.0'
end

Usage

import SwiftyJSON

let json = JSON(jsonObject)

let id = json["Id"].intValue
let name = json["Name"].stringValue
let lat = json["Latitude"].stringValue
let long = json["Longitude"].stringValue
let address = json["Address"].stringValue
            
print(id)
print(name)
print(lat)
print(long)
print(address)
Timchang Wuyep
  • 629
  • 7
  • 10