Validate geojson before creating a Mapbox MGLShape - ios

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

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

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

Unable to parse array of dictionary inside string

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
}

Extract data from GeoJSON

I am trying to retrieve the featureclass inside the GeoJSON below.
I have updated the GeoJSON below.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"scalerank": 8,
"name": "Grill",
"website": "www.rocargo.com/SanNicolas.html",
"natlscale": 5,
"featureclass": "Meat"
},
"geometry": {
"type": "Point",
"coordinates": [-11.1086263, 59.1438153]
}
},
{
"type": "Feature",
"properties": {
"scalerank": 8,
"name": "Queen Vic",
"website": "www.rocargo.com/SanNicolas.html",
"natlscale": 5,
"featureclass": "Fish"
},
"geometry": {
"type": "Point",
"coordinates": [-11.1190539, 59.1498404]
}
},
{
"type": "Feature",
"properties": {
"scalerank": 8,
"name": "Josephines",
"website": "www.rocargo.com/SanNicolas.html",
"natlscale": 5,
"featureclass": "Bar"
},
"geometry": {
"type": "Point",
"coordinates": [-11.1145087,59.142496]
}
},
{
"type": "Feature",
"properties": {
"scalerank": 8,
"name": "Fall",
"website": "www.rocargo.com/SanNicolas.html",
"natlscale": 5,
"featureclass": "Port"
},
"geometry": {
"type": "Point",
"coordinates": [-11.1174109, 59.1402164]
}
}
]
}
The below function can pull all the information above.
func pleaseWork() {
let urlBar = Bundle.main.path(forResource: "bars", ofType: "geojson")!
if let jsonData = NSData(contentsOfFile: urlBar) {
do {
if let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
if let responseA : NSArray = jsonResult["features"] as? NSArray {
print(responseA)
}
}
}
catch { print("Error while parsing: \(error)") }
}
I can pull all the information however, I am struggling to get the 'featureclass' information. What steps am I missing?
Thanks.
asdfadsfadsfdsafdsafdsfadsfdsafdsafdasf asdfasdfadsfdsa
I recommend to use Decodable in Swift 4. It's very simple and convenient
Create the structs
struct Collection : Decodable {
let type : String
let features : [Feature]
}
struct Feature : Decodable {
let type : String
let properties : Properties
// there is also geometry
}
struct Properties : Decodable {
let scalerank : Int
let name : String
let website : URL
let natlscale : Int
let featureclass : String
}
Decode the data and print the values for name and featureclass
let urlBar = Bundle.main.url(forResource: "bars", withExtension: "geojson")!
do {
let jsonData = try Data(contentsOf: urlBar)
let result = try JSONDecoder().decode(Collection.self, from: jsonData)
for feature in result.features {
print("name", feature.properties.name, "featureclass", feature.properties.featureclass)
}
} catch { print("Error while parsing: \(error)") }
Step by Step only, you can achieve that.
if let responseA : NSArray = jsonResult["features"] as? NSArray {
for dictVal in 0..<responseA.count
{
let featuresDict = responseA[dictVal] as! NSDictionary
let propertiesDict = featuresDict.value(forKey: "properties") as! NSDictionary
let featureClassName = propertiesDict.value(forKey: "featureclass") as! String
print(featureClassName)
}
}
Try to use this link for Complex JSON validation. You will get clarity.

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