SwiftyJSON is an extremely useful add-on for Swift, importable via various methods (CocoaPods, Carthage, etc.) and I use it within many of my projects as they commonly require JSON files. So I wanted a nice simple function that I could call with necessary arguments and get my raw String value from my JSON file.
Step 1. We will create one protocol with one constructor method in it and Model class
protocol JSONable {
init?(parameter: JSON)
}
class Style: JSONable {
let ID :String!
let name :String!
required init(parameter: JSON) {
ID = parameter["id"].stringValue
name = parameter["name"].stringValue
}
/* JSON response format
{
"status": true,
"message": "",
"data": [
{
"id": 1,
"name": "Style 1"
},
{
"id": 2,
"name": "Style 2"
},
{
"id": 3,
"name": "Style 3"
}
]
}
*/
}
Step 2. We will create extension of JSON which will convert JSON to model class type object
extension JSON {
func to<T>(type: T?) -> Any? {
if let baseObj = type as? JSONable.Type {
if self.type == .array {
var arrObject: [Any] = []
for obj in self.arrayValue {
let object = baseObj.init(parameter: obj)
arrObject.append(object!)
}
return arrObject
} else {
let object = baseObj.init(parameter: self)
return object!
}
}
return nil
}
}
Step 3. Use code with Alamofire or other code.
Alamofire.request(.GET, url).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
var styles: [Style] = []
if let styleArr = json["data"].to(type: Style.self) {
styles = styleArr as! [Style]
}
print("styles: \(styles)")
case .failure(let error):
print(error)
}
}
I hope this will be useful.
Please refer to this link for more information on this.
https://github.com/SwiftyJSON/SwiftyJSON/issues/714
Here is the function I created, feel free to use it yourself (does of course require SwiftJSON to be correctly integrated within your Xcode Swift project)...
func getJSON(value: [String], fileName: String) -> String{
guard let path = Bundle.main.path(forResource: fileName, ofType: "json"),
let jsonData = NSData(contentsOfFile: path) else{
print("Couldn't find the file on disk or failed to read it")
return "ERR."
}
let jsonObject = JSON(data: jsonData as Data)
guard let jsonValue = jsonObject[value].string else{
print("FAILED to find JSON object")
return "ERR."
}
return jsonValue
}
An example usage of this function would be let myJsonValue = getJSON(value: ["people","person1"], fileName: "database") which would get the value person1 from the people group in the JSON file named database.json. So, if the database.json file looked something like this
{
"people" : {
"person1" : "Bob",
"person2" : "Steve",
"person3" : "Alan",
}
}
the function would return a value of "Bob"
Hope this is a help to anyone, or if you have any suggestions for it then please let me know! Constructive criticism appreciated always.
Related
i'm using Moya library to calling api in my project.
now one of apis required to pass raw json object(multiples object as single object) with POST request in body. it is working fine in postman.
check below screenshot,
also check raw body json,
{
"geometry": {
"location": {
"lat": "22.7195687",
"lng": "75.8577258"
}
},
"icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/geocode-71.png",
"name": "Indore",
"photos": [
{
"photo_reference": "Aap_uECCOLowEnJ2yBUzF0nwRjV5jBx2_JWsofVosuLVvlr-ClIMHNR5-QGIe4phK-3_Bj_laHD_XH_LvlmGDzm33KvxuO1XzaZocxTLOVUdSGI3_otXvpx_FbuzmwiibZiylQEMkekTLKbLdXjK8H3w10nOcoJE-InDVvf5P7Cvyum_kk9k"
}
],
"place_id": "ChIJ2w1BG638YjkR9EBiNdrEbgk",
"reference": "ChIJ2w1BG638YjkR9EBiNdrEbgk",
"types": [
"locality",
"political"
],
"vicinity": "Indore"
},
{
"geometry": {
"location": {
"lat": "22.7429365",
"lng": "75.8867267"
}
},
"icon": "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/generic_business-71.png",
"name": "Visual Maker",
"photos": [
{
"photo_reference": "Aap_uED84yCmvAirxt-dSdPPSE3O_eBSunEiSOM1Uzr0kNNMiJBVvPtBuCuwck2Ek0CDg7S8JP09Iva3Rjhq63O1Tyql_CTeMRF_GWC19QfZUFwwvadcRbfLWo6Wqn4ndCTCh5A6RV212PJcB0HZqe6YV7FphiV_XjkP9pCvk5JLDKNrvOXz"
}
],
"place_id": "ChIJGwLEIlr9YjkRnr8uTQiQ8KU",
"reference": "ChIJGwLEIlr9YjkRnr8uTQiQ8KU",
"types": [
"university",
"general_contractor",
"point_of_interest",
"establishment"
],
"vicinity": "behind Anop Cinema, K/112, LIG Colony, Indore"
},
}
this is two object but it can be multiples objects
below is my Moya class for api calling.
import Moya
import Result
import Alamofire
import UIKit
private func JSONResponseDataFormatter(_ data: Data) -> Data {
do {
let dataAsJSON = try JSONSerialization.jsonObject(with: data)
let prettyData = try JSONSerialization.data(withJSONObject: dataAsJSON, options: .prettyPrinted)
return prettyData
} catch {
return data // fallback to original data if it can't be serialized.
}
}
private extension String {
var urlEscaped: String {
return self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
}
}
let MyAPIProvider = MoyaProvider<EBabuAPI>( plugins: [VerbosePlugin(verbose: true)])
enum MyAPI {
case topPlaceVibeAvg(geometry : [Any], icon:[String], name:[String], photos:[Any], placeIds:[String], reference:[String], types:[Any], vicinity:[Any])
}
extension EBabuAPI: TargetType {
var headers: [String : String]? {
switch self {
default:
return ["Authorization": SharedPreference.authToken()]
}
}
var baseURL : URL {
return URL(string: Constants.API.baseURL)! //["Authorization": "Bearer \(Helpers.userToken())"]
}
var path: String {
switch self {
case .topPlaceVibeAvg:
return "places/topPlaceVibeAvg"
}
}
public var method: Moya.Method {
switch self {
case .topPlaceVibeAvg :
return .post
}
}
var sampleData: Data {
return "".data(using: .utf8)!
}
var task: Task {
switch self {
case .topPlaceVibeAvg(let geometry,let icon, let name, let photos, let placeIds, let reference, let types, let vicinity):
return .requestParameters(parameters: ["geometry":geometry, "icon":icon, "name":name, "photos":photos, "place_id":placeIds, "reference":reference, "types":types, "vicinity":vicinity], encoding: JSONEncoding.default)
}
}
func dicToStrig(data : AnyObject) -> String {
do {
let jsonData = try JSONSerialization.data(withJSONObject: data, options: [])
let jsonString = String(data: jsonData, encoding: String.Encoding.ascii)!
return jsonString
} catch {
//handle error
print("error",error)
}
return ""
}
var parameterEncoding: ParameterEncoding {
switch self {
default:
return JSONEncoding.default
}
}
}
struct JsonArrayEncoding: Moya.ParameterEncoding {
public static var `default`: JsonArrayEncoding { return JsonArrayEncoding() }
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var req = try urlRequest.asURLRequest()
let json = try JSONSerialization.data(withJSONObject: parameters!["jsonArray"]!, options: JSONSerialization.WritingOptions.prettyPrinted)
req.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
req.httpBody = json
return req
}
}
as everyone can see above code i'm trying to call topPlaceVibeAvg api with their keys & values api calling successfully but it does not provide proper response same as postman screenshot.
here is api Response:
{
data = (
);
message = "average fetched successfully!";
statusCode = 200;
success = 1;
}
i'm using below ViewModel(placesViewModel) to call api.
var icon = [String]()
var name = [String]()
var name = [Any]()
var placeIds = [String]()
var reference = [String]()
var testTypes = [Any]()
var vicinity = [Any]()
var geometry = [Any]()
var photos = [Any]()
func topPlaceVibeAvg(_ completion: #escaping ((JSONDictionary) -> ())) {
if !isPullToRefresh { Loader.show("Looking around....") }
APIController.makeRequest(.topPlaceVibeAvg(geometry: geometry, icon: icon, name: name, photos: photos, placeIds: placeIds, reference: reference, types: types, vicinity: vicinity)) { (data, error) in
Loader.hide()
if error == nil {
completion(data!)
} else {
ToastAndAlertManager.showToastWithTitle((error?.desc)!, colorCode: ToastColor.error.rawValue)
}
}
}
here is my ViewController class for api
placesViewModel.geometry = self.nearByPlacesArray.map{$0.geometry}
placesViewModel.icon = self.nearByPlacesArray.map{$0.icon}
placesViewModel.name = self.nearByPlacesArray.map{$0.name}
placesViewModel.photos = self.nearByPlacesArray.map{$0.photos}
placesViewModel.placeIds = self.nearByPlacesArray.map{$0.placeId}
placesViewModel.reference = self.nearByPlacesArray.map{$0.reference}
placesViewModel.testTypes = self.nearByPlacesArray.map{$0.types}
placesViewModel.vicinity = self.nearByPlacesArray.map{$0.vicinity}
placesViewModel.topPlaceVibeAvg { (data) in
print(data)
}
Note : **nearByPlacesArray** is main array & i'm to sorting my required array object from here
I'm really tried with problem, i've already search regarding this thing but got no answers.
AnyOne have idea about this?
To send complex parameters in the request body you can use the Moya function:
requestCompositeData(bodyData: Data, urlParameters: [String: Any])
Moya have this comment for this function:
A requests body set with data, combined with url parameters
So this function allow us send anything in the request body.
For example something like this is a good aproach:
import Moya
import SwiftyJSON
// Data task in MoyaProvider
var task: Task {
switch self {
case .categoriesAndSubCategories:
let graphQLJson = JSON([
[
"query": "query categories { categories { id, title, description, slug, adsStats, subcategories { id, title, slug } } }"
]
])
return .requestCompositeData(bodyData: try! graphQLJson.rawData(), urlParameters: [:])
}
}
How to parse this json in my code? (Как распарсить этот json в моем коде?). What data model to collect? (какую модель данных собирать?). I don’t understand how to cast dictionaries in dictionaries later. (Не пойму как потом кастить словари в словарях).
I get an error opposite let artist:
Value of type 'Dictionary.Element' (aka '(key: String, value: Dictionary)') has no subscripts
func fetchCurrentChartsWithAlamofire(apiMethod: String) {
let url = "https://"
request(url).validate().responseJSON { responseData in
switch responseData.result {
case .success(let value):
guard let jsonData = value as? [String:[String:AnyObject]] else { return }
for artists in jsonData {
let artist = Artist(name: artists["artists"])
}
case .failure(let error):
print(error)
}
}
}
Here is json in the browser:
{
"artists": {
"artist": [
{
"name": "The Weeknd",
}
]
}
}
Here is how you can parse this
struct Artist:Decodable {
let artists:Artists
}
struct Artists:Decodable {
let artist: [ArtistName]
}
struct ArtistName:Decodable {
let name: String
}
For json
So I'm trying to parse Google Books API respond. I want to get title, description, thumbnailUrl, authors and published data. Here is the problem :
func getBooksFrom(completion: #escaping (Result<[[String: AnyObject]]>) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else { return completion(.Error(error!.localizedDescription)) }
guard let data = data else { return
completion(.Error(error!.localizedDescription)) }
do {
if let json = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers]) as? [String: AnyObject] {
if let items = json["items"] as? [[String: AnyObject]] {
DispatchQueue.main.async {
completion(.Succes(items))
}
}
}
} catch let error {
print(error.localizedDescription)
return completion(.Error(error.localizedDescription))
}
}.resume()
}
And on my View Controller in the ViewDidLoad i have
let service = ApiService()
service.getBooksFrom { (result) in
switch result {
case .Succes(let data):
self.parseData(array: data)
case .Error(let message):
self.showAlertWith(title: "Error", and: message)
}
}
So that's pretty simple parsing, but...
When I want to map items into Book Object i have to :
func parseData(_ data: [[String: AnyObject]]) -> [Book]{
for item in data {
if let volumeInfo = item["volumeInfo"] as? [String: AnyObject] {
let books = data.map { (jsonDictionary) -> Book in
let title = volumeInfo["title"] as? String ?? ""
let publishedData = volumeInfo["publishedDate"] as? String ?? ""
let authors = volumeInfo["authors"] as? [String] ?? [""]
let description = volumeInfo["description"] as? String ?? ""
let newBook = Book(title: title, publishedData: publishedData, description: description)
return newBook
}
return books
}
}
return [Book]()
}
Which is super awful way to do it.. You have to return Book on the bottom, because of the for-loop, and
VolumeInfo is next Dictionary, so I really don't know exactly how to map it and get for example authors, because it's next Array..
One sample JSON object:
{
"items":[
{
"volumeInfo":{
"title":"The Ancestor's Tale",
"subtitle":"A Pilgrimage to the Dawn of Life",
"authors":[
"Richard Dawkins",
"Yan Wong"
]
"publishedDate":"2016-04-28",
"description":"A fully updated ",
"imageLinks":{
"smallThumbnail":"http://books.google.com/books/content?id=vzbVCQAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail":"http://books.google.com/books/content?id=vzbVCQAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
}
}
]}
So this is quite simple when you have array of String : Value, but how should you map in proper way, when you have for example dictionaries in dictionary VolumeInfo or array of strings like authors?
I personally find the way to parse objects in swift with URLSession relatively clumsy. Whenever I can I use Alamofire in combination with the AlamofireObjectMapper.
This allows you to create a simple object. For example:
class Book: Mappable {
var title: String?
var subtitle: String?
var description: String?
required init?(map: Map){
}
func mapping(map: Map) {
title <- map["title"]
subtitle <- map["subtitle"]
description <- map["description"]
}
}
When you make a request, you can then use the responseObject method to directly parse your object and assign the proper types.
Alamofire.request(URL).responseObject { (response: DataResponse<Book>) in
let book = response.result.value
print(book?.title)
}
For this example, I simply parsed only one book. But the concept can also be easily extended to arrays or nested json objects. I personally find this leads to much cleaner code than using URLSession directly.
I want to get my CampaignList from JsonObject which I parsed before. But it gives fatal error while it is running.
Error
"fatal error: unexpectedly found nil while unwrapping an Optional value"
self.CampaignArray = Campaigns as! NSMutableArray
The code :
var CampaignArray:NSMutableArray = []
func Get(){
let res: String = ""
let jsonObject = ["PhoneNumber": "xxxxx"]
let Jsn = JsonClass(value: jsonObject, text: res)
Alamofire.request(.POST, "http://MYURL",parameters: jsonObject,
encoding: .JSON).validate(statusCode: 200..<303)
.validate(contentType: ["application/json"])
.responseJSON { (response) in
NSLog("response = \(response)")
switch response.result {
case .Success:
guard let resultValue = response.result.value else {
NSLog("Result value in response is nil")
//completionHandler(response: nil)
return
}
let responseJSON = resultValue
print(responseJSON)
let result = Jsn.convertStringToDictionary(responseJSON as! String)!
print("result: \(result)")
let Campaigns = (result as NSDictionary)["Campaigns"]
print(Campaigns)
self.CampaignArray = Campaigns as! NSMutableArray
let notifications = (result as NSDictionary)["Notifications"]
print(notifications)
break
case .Failure(let error):
NSLog("Error result: \(error)")
// Here I call a completionHandler I wrote for the failure case
return
}
}
}
And my response Json is:
json: {"CampaignList":[
{"Bonus":"5","CampaignId":"zQUuB2RImUZlcFwt3MjLIA==","City":"34"}
{"Bonus":"3","CampaignId":"VgYWLR6eL2mMemFCPkyocA==","City":"34"}],
"MemberId":"ZBqVhLv\/c2BtMInW52qNLg==",
"NotificationList":[{"Notification":"Filiz Makarnadan Milli Piyango Çekiliş Hakkı Kazanmak İster misin ?","PhoneNumber":"555555555"}]}
JSON you provide is invalid. There is lack of , in Campaigns dictionary.
Valid JSON looks like:
{
"CampaignList": [
{
"Bonus": "5",
"CampaignId": "zQUuB2RImUZlcFwt3MjLIA==",
"City": "34"
},
{
"Bonus": "3",
"CampaignId": "VgYWLR6eL2mMemFCPkyocA==",
"City": "34"
}
],
"MemberId": "ZBqVhLv/c2BtMInW52qNLg==",
"NotificationList": [
{
"Notification": "Filiz Makarnadan Milli Piyango Çekiliş Hakkı Kazanmak İster misin ?",
"PhoneNumber": "555555555"
}
]
}
fatal error: unexpectedly found nil while unwrapping an Optional value
You receive this error because you try cast nil to NSDictionary object.
You have no Campaigns key in JSON you provide, so when you try to get this key from JSON you get nil. In next step you try cast this nil to NSDictionary.
Try use CampaignList key to get data you want.
let result: [String: AnyObject] = Jsn.convertStringToDictionary(responseJSON as! String)!
let campaigns: [Campaign] = result["CampaignList"] as! [Campaign]
print(Campaigns)
self.CampaignArray = campaigns
let notifications = result["NotificationList"]
print(notifications)
The same case will be with your Notifications key from JSON Dictionary.
You should also use swift types over objective-c NSDictionary and NSArray.
Try to use SwiftyJSON (https://github.com/SwiftyJSON/SwiftyJSON)
This library (pod) is very simple and has detailed documentation.
Your example:
I put son file "data.json" in my project and read it. Thank Daniel Sumara for your json example correction.
if let path = NSBundle.mainBundle().pathForResource("data", ofType: "json") {
if let data = NSData(contentsOfFile: path) {
let json = JSON(data: data)
if let CampaignList = json["CampaignList"].array {
for index in 0 ..< CampaignList.count {
print("Campaign [\(index)]")
if let CampaignId = CampaignList[index]["CampaignId"].string {
print(" CampaignId: \(CampaignId)")
}
if let City = CampaignList[index]["City"].string {
print(" City: \(City)")
}
if let Bonus = CampaignList[index]["Bonus"].string {
print(" Bonus: \(Bonus)")
}
}
}
if let MemberId = json["MemberId"].string {
print("MemberId: \(MemberId)")
}
if let NotificationList = json["NotificationList"].array {
print("NotificationList")
for notification in NotificationList {
if let Notification = notification["Notification"].string {
print(" Notification: \(Notification)")
}
if let PhoneNumber = notification["PhoneNumber"].string {
print(" PhoneNumber: \(PhoneNumber)")
}
}
}
}
}
Also you can use Alamofire-SwiftyJSON (https://github.com/SwiftyJSON/Alamofire-SwiftyJSON)
P.S. you have fatal errors because you do not check if value is nil. read about "if let" expression (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html)
The issue is i am not able to populate an array with what I thought would be correct code to parse a JSON file with swiftyJSON.
As well as I am not sure if the process at which i send the request is correct.
JSON format: this should be simplified to what it truly represents: A dictionary with a "string" key and value of an array of dictionaries. then a string key with a value of a dictionary. then a string with a value of a string which i need.
{
"string1" : [
{ "string2" : {
"string3" : "DataINeed"
}
}
]
}
my code
func downloadSecondJSONData(completed: DownloadComplete)
{
let API_ServerKey = "My API Key"
let URL_Search = "URL Address"
let urlString = "\(URL_Search)"
let url = NSURL(string: urlString)!
Alamofire.request(.GET,url).responseJSON { (response) -> Void in
switch response.result
{
case .Success:
if let value = response.result.value
{
let json = JSON(value)
if let data = json["string1"]["string2"]["string3"].string
{
self.dataArray.append(data)
}
}
case .Failure(let error):
print(error)
}
completed()
}
}
func printData()
{
print(self.dataArray.count)
}
How I am attempting to call the methods
downloadFirstJSONData { () -> () in
self.randomMethod() //data i use with the downloadFirstJSONData is required to use in the downloadSecondJSONData
self.downloadSecondJSONData({ () -> () in
self.printData() //check if data has been stored
})
}
Looks like you're not accessing the array after the first dictionary.
I guess the safe way to access the first object would be:
if let array = json["string1"].array,
let firstDict = array.first,
let data = firstDict["string2"]["string3"].string {
self.dataArray.append(data)
}
But I suppose with SwiftyJSON you can also do a variation of what you had:
if let data = json["string1"][0]["string2"]["string3"].string {
self.dataArray.append(data)
}