Use Swifty JSON to parse array - ios

This is my json to parse (example):
[
{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
},
{
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}
]
paste it this json viewer to view it easily.
as you can see, it is an array (of teams).
I need to get each team and do something with it.
After many attempts, I tried using SwiftyJSON, because I thought it will be easier. But, it did not worked for me.
This is what I tried:
let array = JSON(response)
// print each subJSON in array
for team in array.arrayValue {
print(team)
}
But the loop does not work. It does not go in to the loop at all.
Maybe it does not understand that my json is an array.
I can see the array object in the debugger. It looks like this:
How can I get these sub-JSONs?
Thanks.

I think you should use
let array = JSON(parseJSON: response)
instead of
let array = JSON(response)

SwiftyJSON contains methods to parse JSON string into a JSON object, check documentation
/**
Parses the JSON string into a JSON object
- parameter json: the JSON string
- returns: the created JSON object
*/
public init(parseJSON jsonString: String) {
if let data = jsonString.data(using: .utf8) {
self.init(data)
} else {
self.init(NSNull())
}
}
/**
Creates a JSON from JSON string
- parameter string: Normal json string like '{"a":"b"}'
- returns: The created JSON
*/
#available(*, deprecated: 3.2, message: "Use instead `init(parseJSON: )`")
public static func parse(json: String) -> JSON {
return json.data(using: String.Encoding.utf8)
.flatMap{ JSON(data: $0) } ?? JSON(NSNull())
}
or alternatively you can convert son string into son object like
Swift 3:
let dataFromString = response.data(using: .utf8)
let jsonArray = JSON(data: dataFromString!)

In the following example, I save team names in an array. I've tested it.
var names = [String]()
if let array = json.array {
for i in 0..<array.count {
let name = array[i]["name"]
names.append(name.stringValue)
}
}
print(names) // ["Team name", "Team name"]

Here is the answer for Swift 5. In My case data response is something like below :
[
{
"Name": "Some Type",
"Data": [
{
"ParentId": 111,
"Code": "Personal",
"SortOrder": 1,
"Name": "Personal",
"Id": 323
},
{
"ParentId": null,
"Code": "Work",
"SortOrder": 2,
"Name": "Work",
"Id": 324
}
],
"KeyType": "Integer"
},
{
"Name": "Phone Type",
"Data": [
{
"ParentId": null,
"Code": "Phone",
"SortOrder": 1,
"Name": "Phone",
"Id": 785
},
{
"ParentId": null,
"Code": "Cell",
"SortOrder": 2,
"Name": "Cell",
"Id": 786
},
{
"ParentId": null,
"Code": "Fax",
"SortOrder": 3,
"Name": "Fax",
"Id": 787
},
{
"ParentId": null,
"Code": "Home",
"SortOrder": 4,
"Name": "Home",
"Id": 788
},
{
"ParentId": null,
"Code": "Office",
"SortOrder": 5,
"Name": "Office",
"Id": 789
}
],
"KeyType": "Integer"
}
]
I was handled it with following code.
struct responseObjectClass:BaseModel {
var responsearray: [arrayData]? = nil
init(json: JSON) {
responsearray = json.arrayValue.map { arrayData(json: $0) }
}
}
struct arrayData:BaseModel {
let Name: String?
var DataValue: [DataLookup]? = nil
let KeyType: String?
init(json: JSON) {
Name = json["Name"].stringValue
DataValue = json["Data"].arrayValue.map { DataLookup(json: $0) }
KeyType = json["KeyType"].stringValue
}
}
struct DataLookup:BaseModel {
let ParentId: Any?
let Code: String?
let SortOrder: Int?
let Name: String?
let Id: Int?
init(json: JSON) {
ParentId = json["ParentId"]
Code = json["Code"].stringValue
SortOrder = json["SortOrder"].intValue
Name = json["Name"].stringValue
Id = json["Id"].intValue
}
}
BaseModel is Optional it's just used for init Json.
protocol BaseModel {
init(json: JSON)
}

Without SwiftyJSON
Below is the valid JSON
data.json File
[{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
}, {
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}]
Below is the code to read your json.
if let path = Bundle.main.path(forResource: "data", ofType: "json") {
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
if let jsonArray = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSArray {
for (_, item) in jsonArray.enumerated() {
let itemDict = item as! NSDictionary
let id = itemDict["id"] as! Int
let name = itemDict["name"] as! String
let shower = itemDict["shower"] as! NSDictionary
let showerId = shower["id"] as! Int
let showerStatus = shower["status"] as! Int
let startLocation = shower["startLocation"] as! NSDictionary
let startLocationId = startLocation["id"] as! Int
let startLocationName = startLocation["name"] as! String
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}

this is what worked for me:
// Convert JSON to Array
func JSONToArray(_ json: String) -> Array<Any>? {
if let data = json.data(using: String.Encoding.utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? Array
} catch let error as NSError {
print(error)
}
}
return nil
}
After using this function i could loop trough the sub JSONs.
Thanks.

Related

get position in a api swift Codable and Model data

i am getting data from an api like this
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
I have my model like this
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
}
But I dont know how to define the "internData", I tried with another Model "InterData" and define id and name like the PersonData, but i get an error, i tried also with [String:Any] but i get an error for the Codable protocol
I am using
let resP = try JSONSerialization.jsonObject(with: data, options: .init()) as? [String: AnyObject]
print("resP", )
in my script of Service/Network
Thanks if somebody knows
You can't use [String:Any] type in case of Codable. you need to create an another model of InternData, which is used by PersonData.
Code:
JSON Data :
let jsonData =
"""
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
"""
// Models
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
let internData : InternData?
}
// New model
struct InternData : Codable {
let id : String?
let name : String?
}
// Parsing
do {
let parseRes = try JSONDecoder().decode([PersonData].self, from: Data(jsonData.utf8))
print(parseRes)
}
catch {
print(error)
}

How to retrieve data from json response

I am trying to get data from json response, and the response format is mentioning below. I want to fetch "recipient" dictionary, and need to show in table.each cell contains name and unique id and image. How to get this dictionary to story in local dictionary?
{
"success": 1,
"status": 200,
"data": {
"chat": [
{
"id": 5,
"status": 0,
"created_at": "2019-02-19 13:29:15",
"updated_at": "2019-02-19 13:29:15",
"recipient": {
"id": 5,
"unique_id": "10004",
"name": "Pandu",
"avatar": "https://www.planetzoom.co.in/img/default_avatar_female.png"
},
"conversation": {
"id": 67,
"chat_id": 5,
"user_id": 4,
"type": 0,
"message": "I have sent a msg now",
"status": 0,
"created_at": "2019-02-26 04:02:20"
}
},
{
"id": 3,
"status": 0,
"created_at": "2019-02-19 13:17:49",
"updated_at": "2019-02-19 13:17:49",
"recipient": {
"id": 8,
"unique_id": "10007",
"name": "Mahitha",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/cZt9yQlBzIEewOdQ1lYZhl3dFiOv2k3bxG7HLOzR.jpeg"
},
"conversation": {
"id": 57,
"chat_id": 3,
"user_id": 4,
"type": 0,
"message": "Hi",
"status": 0,
"created_at": "2019-02-24 13:04:29"
}
},
{
"id": 2,
"status": 0,
"created_at": "2019-02-19 07:59:05",
"updated_at": "2019-02-19 07:59:05",
"recipient": {
"id": 1,
"unique_id": "1111",
"name": "Angadi World Tech",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/NlVzdUAfmLfIG9677szYZz7NkWyY4ULHAqnlCiiV.png"
},
"conversation": {
"id": 21,
"chat_id": 2,
"user_id": 4,
"type": 0,
"message": "Hi\\uD83D\\uDE0A",
"status": 0,
"created_at": "2019-02-21 10:35:26"
}
}
]
}
}
The best way to decode json in my opinion is to use Codable
I've created a few structs to represent the data and to decode it, please note this json wasn't valid so have had to wrap it in {}
Here's the json:
let jsonString = """
{
"chat": [
{
"id": 12,
"status": 0,
"created_at": "2019-02-22 04:57:12",
"updated_at": "2019-02-22 04:57:12",
"recipient": {
"id": 26,
"unique_id": "10024",
"name": "Kaverinew",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/1PyI4ceM3zPsG1fxbfatktWUT75sOE2Ttah8ctIp.png"
},
"conversation": {
"id": 65,
"chat_id": 12,
"user_id": 4,
"type": 1,
"message": "https://www.planetzoom.co.in/storage/chat/message/e759KWdSBegwXslAoS2xst0lohbbjNZMdpVnbxQG.png",
"status": 0,
"created_at": "2019-02-25 15:39:24"
}
},
{
"id": 6,
"status": 0,
"created_at": "2019-02-20 07:16:35",
"updated_at": "2019-02-20 07:16:35",
"recipient": {
"id": 7,
"unique_id": "10006",
"name": "Hema",
"avatar": "https://www.planetzoom.co.in/img/default_avatar_female.png"
},
"conversation": {
"id": 44,
"chat_id": 6,
"user_id": 4,
"type": 1,
"message": "https://www.planetzoom.co.in/storage/chat/message/qJjOtCRcBKBuq3UKaKVuVOEIQhaVPeJr3Bd4NoLo.png",
"status": 0,
"created_at": "2019-02-22 10:17:49"
}
}
]
}
Here are the structs:
struct Recipient: Codable {
var identifier: Int
var unique_id: Int
var name: String
var avatar: String
enum CodingKeys: String, CodingKey {
case identifier = "id"
case unique_id = "unique_id"
case name = "name"
case avatar = "avatar"
}
}
struct Conversation: Codable {
var identifier: Int
var chat_id: Int
var user_id: Int
var conversationType: Int
var message: String
var status: Int
var created_at: String
enum CodingKeys: String, CodingKey {
case identifier = "id"
case chat_id = "chat_id"
case user_id = "user_id"
case conversationType = "type"
case message = "message"
case status = "status"
case created_at = "created_at"
}
}
struct Chat: Codable {
var identifier: Int
var status: Int
var created_at: String
var updated_at: String
enum CodingKeys: String, CodingKey {
case identifier = "id"
case status = "status"
case created_at = "created_at"
case updated_at = "updated_at"
}
}
struct RootObject: Codable {
var chats: [Chat]
enum CodingKeys: String, CodingKey {
case chats = "chat"
}
}
And here is how you decode it:
if let jsonData = jsonString.data(using: .utf8) {
do {
let root = try JSONDecoder().decode(RootObject.self, from: jsonData)
} catch {
print(error)
}
}
Assuming you already have a Data object representing your JSON you can use JSONSerialization to convert it to concrete Swift object. Once that is done you simply need to go step by step and extract the data. Something like the following should work nicely:
func retrieveRecipients(jsonData: Data?) throws -> [[String: Any]] {
guard let data = jsonData else { throw NSError(domain: "Parsing Recipients", code: 404, userInfo: ["dev_message": "Null JSON data inserted"]) }
guard let parsedJSON = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { throw NSError(domain: "Parsing Recipients", code: 500, userInfo: ["dev_message": "Data could not be parsed as JSON"]) }
guard let object = parsedJSON as? [String: Any] else { throw NSError(domain: "Parsing Recipients", code: 500, userInfo: ["dev_message": "Parsed JSON is not a dictionary"]) }
guard let items = object["chat"] as? [[String: Any]] else { throw NSError(domain: "Parsing Recipients", code: 404, userInfo: ["dev_message": "JSON is missing \"chat\" array"]) }
return items.compactMap { $0["recipient"] as? [String: Any] }
}
This is all the safety enabled. Otherwise you could do it very shortly:
func retreiveRecipientsStrict(jsonData: Data?) -> [[String: Any]] {
return ((try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: Any])["chat"] as! [[String: Any]]).compactMap { $0["recipient"] as? [String: Any] }
}
But this will crash if there is a mistake and it will be extremely hard to debug what went wrong.

how can I filter student with all details whose "active" value is "true"?

I want to filter all details(school, city, name, active) whose active value is true. I have stored value of key "details"
let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any>
{
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "1student", "Active": false },
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true },
{ "name": "4student", "Active": false },
{ "name": "5student", "Active": false}
]
}
}
Expected Result
[
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true }
]
}
]
You can use filter
if let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any> ,
let detailDic = details["details"] as? [String:Any],
let students = detailDic["students"] as? [[String:Any]] {
let activeStudents = students.filter { (item) -> Bool in
guard let active = item["Active"] as? Bool else {return false}
return active
}
print(activeStudents)
}
or you can use shourthand
if let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any> ,
let detailDic = details["details"] as? [String:Any],
let students = detailDic["students"] as? [[String:Any]] {
let activeStudents = (details["students"] as?
[[String:Any]])?.filter{ $0["Active"] as? Bool == true}
print(activeStudents)
}
You start realising the true elegance of Swift once you start turning this into regular objects using Codable. This will let you do things as in the Playground:
import Cocoa
let jsonData = """
{
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "1student", "Active": false },
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true },
{ "name": "4student", "Active": false },
{ "name": "5student", "Active": false}
]
}
}
""".data(using: .utf8)!
struct Student : Codable {
let name: String
let active: Bool
enum CodingKeys: String, CodingKey {
case name
case active = "Active"
}
}
struct School : Codable {
let code: Int
let school: String
let students: [Student]
}
struct Details: Codable {
let details: School
}
do {
let det = try JSONDecoder().decode(Details.self, from: jsonData)
print(det)
let activeStudents = det.details.students.filter({(student)->Bool in student.active})
print(activeStudents)
} catch {
print(error)
}
This is obviously much easier to understand and Xcode can also support you much better during the process. The effort spent on the parser is minimal and easily recovered by the sheer elegance and clarity of the final filtering line.

Parsing Nested JSON SWIFT 4

Could anyone tell me what I am doing wrong?
Here is JSON I'm trying to parse:
{
"results": {
"AF": {
"alpha3": "AFG",
"currencyId": "AFN",
"currencyName": "Afghan afghani",
"currencySymbol": "؋",
"id": "AF",
"name": "Afghanistan"
},
"AI": {
"alpha3": "AIA",
"currencyId": "XCD",
"currencyName": "East Caribbean dollar",
"currencySymbol": "$",
"id": "AI",
"name": "Anguilla"
}
}
}
My code :
class Results: Codable {
let results: [Country]
init(results: [Country]) {
self.results = results
}
}
class Country: Codable {
let currencyId: String
let currencyName: String
let currencySymbol: String
let id: String
let name: String
init(currencyId :String, currencyName: String, currencySymbol: String, id: String, name: String ) {
self.currencyId = currencyId
self.currencyName = currencyName
self.currencySymbol = currencySymbol
self.id = id
self.name = name
}
}
I have looked at Apple's Documentation on decoding nested structs, but I still do not understand how to do the different levels of the JSON properly.
Thanks.
Check the outlined value for the key "results".
"results": {
...
}
{...} represents JSON object. A Swift struct (or class if you think it's better) would be appropriate for JSON object in some cases.
In other cases, Swift Dictionary may be more appropriate.
And each value of this JSON object takes this form:
{
"alpha3": ...,
"currencyId": ...,
"currencyName": ...,
"currencySymbol": ...,
"id": ...,
"name": ...
}
which matches your Country.
So, you just need to change the type of results in your Results class.
class Results: Codable {
let results: [String: Country]
init(results: [String: Country]) {
self.results = results
}
}
Having the same name (without case) for a property and its class might cause some confusion in the future, but I keep it as is as for now.
You can test it like this:
(Assuming to be tested in the Playground with modified Results and your Country.)
let jsonText = """
{
"results": {
"AF": {
"alpha3": "AFG",
"currencyId": "AFN",
"currencyName": "Afghan afghani",
"currencySymbol": "؋",
"id": "AF",
"name": "Afghanistan"
},
"AI": {
"alpha3": "AIA",
"currencyId": "XCD",
"currencyName": "East Caribbean dollar",
"currencySymbol": "$",
"id": "AI",
"name": "Anguilla"
}
}
}
"""
let jsonData = jsonText.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let results = try decoder.decode(Results.self, from: jsonData)
print(results) //-> __lldb_expr_1.Results
} catch {
print(error)
}

Swift2 - Using Alamofire 3 in nested JSON

I have the following JSON:
{
"_embedded": {
"modifier_groups": [
{
"_embedded": {
"options": [
{
"id": "8kT9KTX7",
"name": "Perfect",
"open": false,
"pos_id": "8kT9KTX7",
"price_per_unit": 0
},
{
"id": "zRcEkcj8",
"name": "Overcooked",
"open": false,
"pos_id": "zRcEkcj8",
"price_per_unit": 0
}
]
},
"id": "eMiy4iR4",
"maximum": 1,
"minimum": 1,
"name": "Temperature",
"required": false
},
{
"_embedded": {
"options": [
{
"id": "E5cpac84",
"name": "Tomato",
"open": false,
"pos_id": "E5cpac84",
"price_per_unit": 0
},
{
"id": "GkiREiyL",
"name": "Cheese",
"open": false,
"pos_id": "GkiREiyL",
"price_per_unit": 100
}
]
},
"id": "kMT85Tay",
"maximum": null,
"minimum": 1,
"name": "Toppings",
"required": false
}
]
},
"count": 2,
"limit": 20
}
So there are modifier group names (e.g. "Temperature" and "Toppings"), and group options (e.g. "Perfect" and "Overcooked" for "Temperature" group).
What I am trying to do is build a [String] such as:
["Temperature - Perfect", "Temperature - Overcooked", "Toppings - Tomato", "Toppings - Cheese"]
What would be the quickest way to go about that?
Currently, I first extract the groups into a [String] using valueForKeyPath:
Alamofire.request(.GET, url, headers: headers, encoding: .JSON)
.responseJSON { response in
switch response.result {
case .Success(let JSON):
let jsonData = JSON as? NSDictionary
let groupNames = jsonData?.valueForKeyPath("_embedded.modifier_groups.name")
But how would I get from there to drilling deeper into the group options so that I append them into the [String]?
UPDATE
I tried this but it's not returning anything:
var mods = [String]()
let modGroups = jsonData?.valueForKeyPath("_embedded.modifier_groups")
if let modGroups = modGroups {
for modGroup in modGroups as! [AnyObject] {
let groupOptions = modGroups.valueForKeyPath("_embedded.options")
if let groupOptions = groupOptions {
for groupOption in groupOptions as! [AnyObject] {
mods.append("\(modGroup) - \(groupOption)")
}
}
}
}
Got it:
var mods = [String]()
let modGroups = jsonData?.valueForKeyPath("_embedded.modifier_groups") as? [NSDictionary]
if let modGroups = modGroups {
for modGroup in modGroups {
let groupOptions = modGroup.valueForKeyPath("_embedded.options") as? [NSDictionary]
if let groupOptions = groupOptions {
for groupOption in groupOptions {
mods.append("\(modGroup.valueForKey("name")!) - \(groupOption.valueForKey("name")!)")
}
}
}
}

Resources