Hasan Nizamani
Hasan Nizamani

Reputation: 1163

Simple and clean way to convert JSON string to Object in Swift

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.

Upvotes: 116

Views: 203676

Answers (17)

RTXGamer
RTXGamer

Reputation: 3722

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

Upvotes: 2

Timchang Wuyep
Timchang Wuyep

Reputation: 831

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)

Upvotes: -1

Shourob Datta
Shourob Datta

Reputation: 2072

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

Upvotes: 4

hardiBSalih
hardiBSalih

Reputation: 61

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

Upvotes: 0

Vitya Shurapov
Vitya Shurapov

Reputation: 2308

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 {}

Upvotes: 4

Just a coder
Just a coder

Reputation: 16730

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

Upvotes: 77

swiftBoy
swiftBoy

Reputation: 35783

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

Upvotes: 11

Danny Narv&#225;ez
Danny Narv&#225;ez

Reputation: 576

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)

Upvotes: 9

Mohamed Saleh
Mohamed Saleh

Reputation: 3287

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

Upvotes: 1

Noyer282
Noyer282

Reputation: 984

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.

Upvotes: 22

Rugmangathan
Rugmangathan

Reputation: 3226

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

Upvotes: 2

Timeless
Timeless

Reputation: 7537

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.

Upvotes: 5

MR_22
MR_22

Reputation: 39

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

Upvotes: 2

Munish Kapoor
Munish Kapoor

Reputation: 3339

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

Upvotes: 3

isair
isair

Reputation: 1860

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!

Upvotes: 7

PassKit
PassKit

Reputation: 12581

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";
}
*/

Upvotes: 28

Maxim Shoustin
Maxim Shoustin

Reputation: 77904

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,

Upvotes: 65

Related Questions