Unable to parse array of dictionary inside string - ios

Currently I am working on bus booking module. After user set his departure and arrival city and date of journey, user will be shown a list of available buses. I have done that part successfully. But the problem which I am facing is that, each bus has it's own cancellation policy which is array of dictionary inside string. I am unable to parse it. Inside every dictionary of "apiAvailableBuses" there is "cancellationPolicy" key which has string as value which contains array of dictionary. I have removed other key value pairs from "apiAvailableBuses".
List of available Buses JSON response:
"apiAvailableBuses":[
{
"cancellationPolicy":"[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"9-12\",\"refundInPercentage\":\"25\"},{\"cutoffTime\":\"12-24\",\"refundInPercentage\":\"35\"},{\"cutoffTime\":\"24-48\",\"refundInPercentage\":\"50\"},{\"cutoffTime\":\"48-60\",\"refundInPercentage\":\"75\"},{\"cutoffTime\":\"60\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
},
{
"cancellationPolicy":"[{\"cutoffTime\":\"6-24\",\"refundInPercentage\":\"70\"},{\"cutoffTime\":\"24\",\"refundInPercentage\":\"85\"}]"
}
]
Can anyone help me with a solution for this? If anyone can't understand my question please let me know.
Note: I am not using Codable in my project.
Thanks in advance.

Use Codable to parse the above JSON response.
If your JSON response have the below format:
{
"apiAvailableBuses": [
{
"cancellationPolicy": [
{
"cutoffTime": "5",
"refundInPercentage": "90"
}
]
}
]
}
Create Codable types to parse the above response.
struct AvailableBuses: Codable {
var apiAvailableBuses: [Bus]
}
struct Bus: Codable {
var cancellationPolicy: [CancellationPolicy]
}
struct CancellationPolicy: Codable {
var cutoffTime: String
var refundInPercentage: String
}
In the above code, I've created 3 struct conforming to Codable protocol - AvailableBuses, Bus, CancellationPolicy
Usage:
After getting data from your API response, you can parse the it using the above created structs like,
if let data = jsonStr.data(using: .utf8) {
do {
let availableBuses = try JSONDecoder().decode(AvailableBuses.self, from: data)
print(availableBuses)
} catch {
print(error)
}
}

If you don't want to use Codable for some reason, you can use JSONSerialization.
let input = "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
let data = input.data(using: .utf8)!
let parsed = try! JSONSerialization.jsonObject(with: data, options: []) as! Array<Dictionary<String, Any>>
print(parsed) // [["refundInPercentage": 90, "cutoffTime": 5]]

You can parse JSON string using the following method:
// JSON Format
let jsonResponse = ["apiAvailableBuses": [
[
"cancellationPolicy": "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"9-12\",\"refundInPercentage\":\"25\"},{\"cutoffTime\":\"12-24\",\"refundInPercentage\":\"35\"},{\"cutoffTime\":\"24-48\",\"refundInPercentage\":\"50\"},{\"cutoffTime\":\"48-60\",\"refundInPercentage\":\"75\"},{\"cutoffTime\":\"60\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"5\",\"refundInPercentage\":\"90\"}]"
],
[
"cancellationPolicy": "[{\"cutoffTime\":\"6-24\",\"refundInPercentage\":\"70\"},{\"cutoffTime\":\"24\",\"refundInPercentage\":\"85\"}]"
]
]
]
// Function Calling
setBuses(json: jsonResponse)
// Function to Parse JSON
func setBuses(json: Dictionary<String,Any>) {
guard let buses = json["apiAvailableBuses"] as? [Dictionary<String,Any>] else { return }
for (index, bus) in buses.enumerated() {
print("\n\nBus #\(index+1)")
guard let policies = convertToDictionary(text: bus["cancellationPolicy"] as! String) else { return }
for (index, policy) in policies.enumerated() {
print("\nPolicy #\(index+1)")
print("cutoffTime #\(index+1) \(String(describing: policy["refundInPercentage"]))")
print("refundInPercentage #\(index+1) \(String(describing: policy["cutoffTime"]))")
}
}
}
func convertToDictionary(text: String) -> [Dictionary<String,Any>]? {
let data = text.data(using: .utf8)!
do {
if let jsonObj = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? [Dictionary<String,Any>] {
return jsonObj
} else {
print("JSON Error")
}
} catch let error as NSError {
print(error)
}
return nil
}

Related

How to parse grouped nested dictionary in swift

Please help me out for parsing the below json response in SWIFT5 as I am getting nil values for the user and group data.
{
"user": {
"0": {
"id": "5",
"name": "ABC"
}
},
"group": {
"0": {
"id": "510",
"name": "XYZ"
}
}
}
if let unwrappedData = data {
do{
let json = try JSONSerialization.jsonObject(with: unwrappedData, options: [])
print(json)
if let user = try? JSONDecoder().decode(UserModel.self,from:unwrappedData){
completion(.success(user))
}else{
let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: unwrappedData)
completion(.failure(errorResponse.errorValue))
}
}catch{
completion(.failure(error))
}
}
The user data is printing as nil. please help me out to resolve the issue.
I tried below code in playground and it works like charm, what is the issue in json?
Data Model
// MARK: - Sample
struct Sample: Codable {
let user, group: Group
}
// MARK: - Group
struct Group: Codable {
let the0: The0
enum CodingKeys: String, CodingKey {
case the0 = "0"
}
}
// MARK: - The0
struct The0: Codable {
let id, name: String
}
Json Data
let jsonData = """
{
"user": {
"0": {
"id": "5",
"name": "ABC"
}
},
"group": {
"0": {
"id": "510",
"name": "XYZ"
}
}
}
""".data(using: .utf8)
Json Parsing
if let data = jsonData {
let object = try? JSONDecoder().decode(Sample.self, from: data)
print("Json Object", object)
}
else {
print("Bad Json")
}
Output
Json Object Optional(SwiftPlayground.Sample(user: SwiftPlayground.Group(the0: SwiftPlayground.The0(id: "5", name: "ABC")), group: SwiftPlayground.Group(the0: SwiftPlayground.The0(id: "510", name: "XYZ"))))

How to parse part of json data into table

How to parse following json which I want to parse only few part.
```
{
"head": {
"StatusValue": 200,
"StatusText": "Success"
},
"body": {
"Data": [
{
"payer_type_id": 1,
"payer_type": "Self Pay"
},
{
"payer_type_id": 2,
"payer_type": "Corporate"
},
{
"payer_type_id": 6,
"payer_type": "Insurance"
}
],
"RecordCount": 3,
"TotalRecords": null
}
}
How to parse only data inside Data key.
Expected result should be in following format
Date = [["payer_type_id": 1,"payer_type": "Self Pay"],["payer_type_id": 2,"payer_type": "Corporate"],["payer_type_id": 6,"payer_type": "Insurance"]]
You can use Codable protocol for your parsing .
Create Your Model
struct APIRootModel : Codable {
let head : HeaderModel
let body : BodyModel
}
struct HeaderModel :Codable{
let StatusValue : Int
let StatusText : String
}
struct BodyModel : Codable{
let Data : [DataModel]
}
struct DataModel : Codable{
let payer_type_id : Int
let payer_type : String
}
Decode your json using JSONDecoder()
let decoder = JSONDecoder()
do {
let rootModel = try decoder.decode(APIRootModel.self, from: jsonData)
print(rootModel.body.data)
} catch {
print(error)
}
Use [DataModel] for your tableview datasource.
Try this:
let jsonString = "your json string here"
let dictObject = getDictionaryFromJsonString(dictString: jsonString)
let bodyDict = dictObject["body"]
let dataArray = bodyDict["Data"]
func getDictionaryFromJsonString(dictString:String)->
[String: Any] {
do {
return try JSONSerialization.jsonObject(with:
dictString.data(using: String.Encoding.utf8,
allowLossyConversion: true)!, options:
JSONSerialization.ReadingOptions.allowFragments) as!
Dictionary
} catch {
return [:]
}
}

Create extension in Swift 5 for Dictonary[String:Any]

I want to create extension for Dictionary [String:Any] which is received from API Response.
Right Now I am doing below way
I have created func getDataFromJson this is working fine, Please let me know how to do that.
func getDataFromJson(json: AnyObject) -> Data?{
do {
print("json = \(json)")
return try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted)
} catch let myJSONError {
print("\n\n\nError => getDataFromJson => \(myJSONError)")
}
return nil;
}
This is my response and I want to "data" to Data
{
"status": true,
"message": "Country List",
"data": [
{
"id": 1,
"name": “ABC”,
"code": "A",
"phone_code": "+91”,
"flag": "country-flags/-shiny.png"
},
{
"id": 2,
"name": “ZYX”,
"code": “Z”,
"phone_code": "+1”,
"flag": "country-flags/-shiny.png"
}
]
}
I want to get data this way jsonResponse["data"].retriveData()
Here is a simple function that encodes the dictionary, the function throws any error so it can be properly handled. Since JSONSerialization.data(withJSONObject: takes an Any parameter this function can also be implemented for an array etc
extension Dictionary {
func retriveData() throws -> Data {
return try JSONSerialization.data(withJSONObject: self)
}
}
Simple example
let dict = ["abc": 123, "def": 456]
do {
let data = try dict.retriveData()
let result = try JSONDecoder().decode([String:Int].self, from:data)
print(result)
} catch {
print(error)
}
Another way is to use Result if you're on Swift 5 (shortened after comment from vadian)
extension Dictionary {
func retriveData() -> Result<Data, Error> {
return Result { try JSONSerialization.data(withJSONObject: self) }
}
}
and an example
let result = try dict.retriveData()
switch result {
case .success(let data):
let dictionary = try JSONDecoder().decode([String:Int].self, from:data)
print(dictionary)
case .failure(let error):
print(error)
}
a copy of your function transposed to an extension can be
extension Dictionary {
func retriveData() -> Data? {
do {
return try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
} catch let myJSONError {
print("\n\n\nError => getDataFromJson => \(myJSONError)")
}
return nil
}
}
Correct json
{
"status": true,
"message": "Country List",
"data": [
{
"id": 1,
"name": "ABC",
"code": "A",
"phone_code": "+91",
"flag": "country-flags/-shiny.png"
},
{
"id": 2,
"name": "ZYX",
"code": "Z",
"phone_code": "+1",
"flag": "country-flags/-shiny.png"
}
]
}
Model
struct Datum: Codable {
let id: Int
let name, code, phoneCode, flag: String
}
Decoding data only
let str = """
{
"status": true,
"message": "Country List",
"data": [{
"id": 1,
"name": "ABC",
"code": "A",
"phone_code": "+91",
"flag": "country-flags/-shiny.png"
},
{
"id": 2,
"name": "ZYX",
"code": "Z",
"phone_code": "+1",
"flag": "country-flags/-shiny.png"
}
]
}
"""
do {
let dic = try JSONSerialization.jsonObject(with: Data(str.utf8)) as! [String:Any]
let content = try JSONSerialization.data(withJSONObject: dic["data"])
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let res = try dec.decode([Datum].self, from: content)
print(res)
}
catch {
print(error)
}
Please try this
// Response Dictionary
let jsonResponse : [String:Any] = ["data":["key1":"value1",
"key2":"value2",
"key3":"value3",
"key4":"value4"]]
// check dictionary contains value for key "data"
if let dataDict = jsonResponse["data"] as? [String:Any] {
// convert dictionary to data
let jsonData = dataDict.retriveData()
print("Json Data :- ", jsonData != nil ? "Success" : "Data is nil")
}
// Dictionary exxtension for converting dictionary to json data
extension Dictionary {
func retriveData() -> Data? {
do {
print("json = \(self)")
return try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.prettyPrinted)
} catch let myJSONError {
print("\n\n\nError => getDataFromJson => \(myJSONError)")
}
return nil;
}
}

Validate geojson before creating a Mapbox MGLShape

I'm using the iOS Mapbox SDK to create a MGLShapeCollectionFeature from a goejson FeatureCollection data that comes from a 3rd party API.
guard let feature = try? MGLShape(data: jsonData, encoding: String.Encoding.utf8.rawValue) as? MGLShapeCollectionFeature else {
print("Could not cast to specified MGLShapeCollectionFeature")
return
}
The problem is that the API sometimes returns an invalid geojson where a single Feature does not contain valid coordinates (see below) and initialising the MGLShape fails with a 'NSInvalidArgumentException', reason: 'A multipoint must have at least one vertex.' which is correct.
Is there a way to filter out and drop those invalid Features within a FeatureCollection other that parsing and fixing the geojson manually?
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"icaoId": "KBOS",
"airSigmetType": "AIRMET",
"hazard": "IFR"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
]
]
}
},
{
"type": "Feature",
"properties": {
"icaoId": "KSLC",
"airSigmetType": "AIRMET",
"hazard": "IFR"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-106.63,
49.06
],
[
-104.12,
48.95
],
[
-104.17,
44.8
],
[
-106.91,
46.38
],
[
-106.63,
49.06
]
]
]
}
}
]
}
A possible solution is to decode the JSON with Codable into structs, filter the empty items and encode the object back:
struct FeatureCollection : Codable {
let type : String
var features : [Feature]
}
struct Feature : Codable {
let type : String
let properties : Properties
let geometry : Geometry
}
struct Properties : Codable {
let icaoId, airSigmetType, hazard : String
}
struct Geometry : Codable {
let type : String
let coordinates : [[[Double]]]
}
do {
var result = try JSONDecoder().decode(FeatureCollection.self, from: jsonData)
let filteredFeatures = result.features.filter{$0.geometry.coordinates != [[]]}
result.features = filteredFeatures
let filteredData = try JSONEncoder().encode(result)
guard let feature = try? MGLShape(data: filteredData, encoding: String.Encoding.utf8.rawValue) as? MGLShapeCollectionFeature else {
print("Could not cast to specified MGLShapeCollectionFeature")
return
}
} catch {
print(error)
}
As you suggested, I did the filtering myself and wrote this extension on Data
extension Data {
func removeEmptyCoordinates() throws -> Data {
guard var geojson = try JSONSerialization.jsonObject(with: self, options: []) as? [String: Any] else {
return self
}
fix(geojson: &geojson,
processFeatureIf: NSPredicate(format: "geometry.type == 'Polygon'"),
keepFeatureIf: NSPredicate(format: "%K[0][SIZE] >= 2", "geometry.coordinates"))
return try JSONSerialization.data(withJSONObject: geojson, options: [])
}
private func fix(geojson: inout [String: Any], processFeatureIf: NSPredicate, keepFeatureIf: NSPredicate) {
guard let type = geojson["type"] as? String, type == "FeatureCollection" else {
// "Not a FeatureCollection"
return
}
// "No features to fix"
guard let features = geojson["features"] as? [[String: Any]] else { return }
let filtered = features.filter { feature in
if !processFeatureIf.evaluate(with: feature) {
// not processing
return true
}
return keepFeatureIf.evaluate(with: feature)
}
geojson["features"] = filtered
}
}

how to show titles(Type) in table view using JSON Data in iOS Swift?

how to show titles(Type) in table view using JSON Data in iOS Swift using the following format
[
{
"Id": 11000,
"Type": "Title1"
},
{
"Id": 11001,
"Type": "Title2"
},
{
"Id": 11002,
"Type": "Title3"
},
{
"Id": 11003,
"Type": "Title4"
}
]
You can use the following function to read titles in an array and use it as data source for tableView.
private func readJson( data : Data) -> [String] {
var titlesArray = [String]()
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
if let jsonDict = json as? [[String: Any]] {
for anObject in jsonDict {
if let title = anObject["Type"] as? String {
titlesArray.append(title)
}
}
} else {
print("JSON is invalid")
}
} catch {
print(error.localizedDescription)
}
return titlesArray
}
I would suggest to use SwiftyJson for json parsing. Instead of doing it manually. You can read about Swifty Json.

Resources