在 Swift 中将 JSON 字符串转换为 Object 的简单而干净的方法

在 Swift 中,我一直在寻找将一个相当简单的 JSON 字符串转换为对象类型的方法,但是没有用。

下面是 Web 服务调用的代码:

func GetAllBusiness() {


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


println(string)


}
}

我有一个迅捷的结构:

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

下面是我部署的测试服务:

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

如果有人能指导我,我会很高兴的。

谢谢。

179164 次浏览

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,

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!

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

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

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

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

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.

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

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.

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

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)

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

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

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

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)

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