I am using graphql for get the API values using Apollo. I have successfully downloaded schema.json and get the values from grpahql. but i can't get the values array of json values.
This is my sample response:
{
"data": {
"team_Dashboard": {
"activeMission": "Food Mission",
"currentMissionChallenges": [
{
"id": "123",
"title": "Challenge",
"estTime": "5",
"location": "Anywhere",
"maxPts": "30",
"status": "Not yet started"
},
{
"id": "1234",
"title": " II Challenge",
"estTime": "5",
"location": "Anywhere",
"maxPts": "70",
"status": "Not yet started"
}
]
}
}
}
Graphql query:
query teamDashboard($teamId: ID!) {
team_Dashboard(teamId: $teamId) {
activeMission
currentMissionChallenges
}
}
Graphql schema response:
missionDeadLine: String
currentMissionChallenges: [JSON]
When i add the currentMissionChallenges([JSON]) in my Graphql query get error response from the server. but When i remove currentMissionChallenges from Graphql query, get success response and values from the server.
The problem is currentMissionChallenges is [JSON] format. When i change my graphql query This is graphql Response
query teamDashboard($teamId: ID!) {
team_Dashboard(teamId: $teamId) {
activeMission
currentMissionChallenges {
id
title
estTime
location
maxPts
status
}
}
}
Following error display in dashBord.grpahql
Field "currentMissionChallenges" must not have a selection since type "[JSON]" has no subfields.
How can i get the json array values from graphql. what is problem for getting Json Values? Please help me!
The best thing about GraphQL is we can use the query as model
Because the response would be as same as the query, so better we can assign the response to a variable of Query type.
Let me explain with an example:-
Suppose if I need to query about my profile data,
Profile.graphql
query MyProfile{
player {
me {
id
secret
name
email
state
country
timezone
picture
pictureType
audio
rank
}
}
countries{
value
viewValue
}
}
Once we'll build the app, it'll create MyProfileQuery in API.swift. In viewController we can use the response as below-
var myProfileData: MyProfileQuery.Data.Player.Me? // Declaring the valiable of player Type
ApolloClientManager.sharedInstance
.fetchQuery(MyProfileQuery(), showLoader: true,
viewController: self) { (response) in // Fetching response using Apollo Client Manager
if let allData = response {
if let profiledata = allData.player?.me {
self.myProfileData = profiledata // Assigning response into the variable declared
self.myEdittingProfileData = profiledata
self.updateUI()
}
if let countryData = allData.countries {
self.allCountrydata = countryData
self.getPickerDataForState(comppletion: {
self.openTimeZonePicker(completion: {
print("got timeZone Data")
})
})
}
}
}
Now we have response into the myProfileData variable which we can use
as follows -
Now we can access all the values we have mentioned in our query as below
print("player id is- \(myProfileData?.id)")
print("player name is- \(myProfileData?.name)")
print("player email is- \(myProfileData?.email)")
print("player state is- \(myProfileData?.state)")
print("player rank is- \(myProfileData?.rank)")
print("player pictureType is- \(myProfileData?.pictureType)")
// player id is- 10
// player name is- jordan
// player email is- jordan#domain.com
// player state is- Ohio
// player rank is- 101
// player pictureType is- custome
//
Hope this will help you out 👍👍👍
i suggest you use custom scalar.
import Apollo
public typealias JSON = [String: Any]
extension Dictionary: JSONDecodable {
public init(jsonValue value: JSONValue) throws {
if let array = value as? NSArray {
self.init()
if var dict = self as? [String: JSONDecodable & JSONEncodable] {
dict["data"] = array as! [[String: Any]]
self = dict as! Dictionary<Key, Value>
return
}
}
guard let dictionary = value as? Dictionary else {
throw JSONDecodingError.couldNotConvert(value: value, to: Dictionary.self)
}
self = dictionary
}
}
var currentMissionChallanges = [JSON]()
func getTeamDashboard(id:String) {
let query = TeamDashboardQuery(id:id)
apollo.fetch(query:query) { [weak self] result, error in
if let dashboards = result.data?.team_dashboard {
if let array = dashboards!["currentMissionChallanges"] as?
[JSON] {
self?.currentMissionChallanges = array
}
}
}
}
Related
Here i have using swiftyJson pod library for response data. normal json response data i could able to get data but for complex i could not make it.
here is my code to get data from response:
private func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse) {
let user = "David****"
let password = "**************"
let loginString = "\(user):\(password)"
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
return
}
let base64LoginString = loginData.base64EncodedString()
print("base 64 login :\(base64LoginString)")
let headers = ["Authorization": "Basic \(base64LoginString)"]
// using URL and request getting a json
let request = URLRequest(url: NSURL(string: path)! as URL)
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = headers
let session = URLSession.init(configuration: config)
session.dataTask(with: request) { (data:Data?, response: URLResponse?, error:Error?) in
if let jsonData = data { // if data has a data and success
do {
let json: JSON = try JSON(data: jsonData)
onCompletion(json,nil)
print("json data:\(json)")
}catch {// error
onCompletion(JSON(),error)
}
} else { // if the data is nil
onCompletion(JSON(),error)
}
}.resume()
}
Used this function in viewController.swift
func addDummyData() {
// Call API
RestApiManager.sharedInstance.getRandomUser { (json:JSON) in
// return json from API
if let results = json["results"].array { // get results data from json
print("results:\(results)")
for entry in results { // save data to items.
self.items.append(UserObject(json: entry))
}
print("array= \(self.items)")
DispatchQueue.main.async { // back to the main que and reload table
self.tableView.reloadData()
}
}
}
}
Model class:
import SwiftyJSON
class UserObject {
// var pictureURL: String!
var username: String!
required init(json: JSON) {
// pictureURL = json["picture"]["medium"].stringValue
username = json["WorkOrder"].stringValue
}
}
Here is my json response:
{
"d": {
"results": [
{
"__metadata": {
"id": "http://*******:****/sap/opu/odata/sap/ZPRJ_PM_APPS_IH_SRV/WorkOrderF4Set('000000504780')",
"type": "ZPRJ_PM_APPS_IH_SRV.WorkOrderF4"
},
"WorkOrder": "000000504780",
"Description": "General Maintenance testing"
},
}
}
From json response i'm trying to get WorkOrder and Description
Any help much appreciated pls....
Please read the JSON carefully. The outermost object is a dictionary with a key d.
To get the results array you have to write
if let results = json["d"]["results"].array { ...
And you don't need a class and never declare properties as IUO which are initialized in an init method
struct User {
let workOrder: String
let description: String
init(json: JSON) {
workOrder = json["WorkOrder"].stringValue
description = json["Description"].stringValue
}
}
Side note: Since Swift 4 SwiftyJSON has become obsolete in favor of Codable. It's built-in and much more efficient.
I know the question sounds weird but I don't know another way to ask this, first of all, I am playing with the Pokemon API and I am new in swift. what is my problem I am parsing the data to show Pokemon information but the endpoint to show pokemon comes like this:
https://pokeapi.co/api/v2/pokemon/
{
"count": 949,
"previous": null,
"results": [
{
"url": "https://pokeapi.co/api/v2/pokemon/1/",
"name": "bulbasaur"
},
{
"url": "https://pokeapi.co/api/v2/pokemon/2/",
"name": "ivysaur"
},
{
"url": "https://pokeapi.co/api/v2/pokemon/3/",
"name": "venusaur"
},
With a name and other URL to see more about a specific pokemon. I could get the dictionary of array and I can show the pokemon name but I don't know how to get the other data that is in the other endpoint.
This is my code for now:
#IBAction func generatePokemon(_ sender: Any) {
// TODO: Improve API request
let apiUrl = URL(string: "https://pokeapi.co/api/v2/pokemon")
let request = URLRequest(url: apiUrl!)
// Request to Pokemon API
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error == nil {
// Optional binding to get data
if let data = data {
let parsedResult: [String:AnyObject]!
do {
parsedResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:AnyObject]
if let resultDictonary = parsedResult["results"] as? [[String:AnyObject]] {
print(resultDictonary[0])
}
} catch {
print("Error in parse json")
}
}
}
}
task.resume()
}
So I am not sure if I need to create another function to get the data of that endpoint and then call that inside my generatePokemon function so I can fill the view with more information? or what is the best way to consume that data.
Here is my repo too if someone wanna see it I have a branch there where I am doing all this first network request.
Github
Thank for your time guys!
Hey I did something like what you need here is my repo:
https://bitbucket.org/pokemonred/pokedexgr2/
Check for the branch pokedexSebas, if you have any questions let me know.
I'm using alamofire to perform the requests.
In the repo you have to take a look into two clases, the first one is: SBBackendManager and SebasObjectMapper.
In the SBBackendManager I have these 2 methods:
func getAllPokemon () { // This will retrieve all pokemon
let url = "https://pokeapi.co/api/v2/pokemon"
Alamofire.request(url).responseObject { (response: DataResponse<SPokemonApiResponse>) in
let pokemonResponse = response.result.value
if let sPokeArray = pokemonResponse?.resultados {
contador = sPokeArray.count
}
}
}
func getPokemon(_ url:String){ // This will retrieve a single pokemon
Alamofire.request(url).responseObject { (response: DataResponse<SPokemon>) in
let spokemon = response.result.value
pokemonArray += [spokemon!]
contador = contador! - 1
}
}
And on SebasObjectMapper I have this:
class SPokemonApiResponse:Mappable{
var resultados:[SPokemonResult]?
required init?(map: Map) { }
func mapping(map: Map) {
resultados <- map["results"]
}
}
class SPokemonResult:Mappable {
var url:String? {
didSet { // HERE: every time a result is added to the array will trigger the get a single pokemon method
let bm = SBackendManager()
bm.getPokemon(url!)
}
}
required init(map:Map) {}
func mapping(map: Map) {
url <- map["url"]
}
}
I am working on an iOS application written in Swift which parse a lot of JSON files.
The JSON structures are sophisticated and I would to test them before to map JSON to object.
For example test if the key 'users' exists and for each user the structure('name', 'first', 'last').
{
"users": [
{
"name": {
"first": "emmi",
"last": "wiita"
}
},
{
"name": {
"first": "erdi",
"last": "nielen"
}
},
{
"name": {
"first": "daniel",
"last": "beck"
}
}
]
}
Are there any good way to do this?
Thanks.
The only way to accomplish that is really opening the JSON file and testing each property.
A good news is that since Swift 2.0 you can use guard to test if you can assign a valid value to a var or let, so you can create a function as follows:
func isValidJSON(data: NSData) -> Bool {
json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
// if "users" exists on the JSON
// AND we can cast it to an array of dictionaries
guard let users = json["users"] as [[String: AnyObject]] else {
return false
}
for user in users {
guard let name = user["name"] as [[String: AnyObject]]
firstName = name["first"] as String,
lastName = name["last"] as String else {
return false
}
}
// valid JSON
return true
}
A best practice would be also to implement the use of Exceptions instead returning false in each guard statement.
Thank you for your post #Felipe Plets. I found a good way to test JSON file.
I have implemented an enum ErrorType(Exception):
/**
Enumeration describing possible errors that occur while parsing
a message from JSON file.
*/
enum ReceiverError: ErrorType {
/**
Error trigge when the key is missed or the type.
- Parameter key: Key missed.
*/
case MissingKeyOrType(key: String)
}
then I can test all the JSON file like this:
func isValidJSON(message: [String: AnyObject]) -> throws {
guard let sosId = message["id"] as? String
else { throw ReceiverError.MissingKeyOrType(key: "sos - id") }
guard let text = message["text"] as? String
else { throw ReceiverError.MissingKeyOrType(key: "text")
}
let json = ... Get JSON
do {
try isValidJSON(json)
} catch CustomReceiver.ReceiverError.MissingKeyOrType(let key) {
print("Error: Missing key or type [key: \(key)].")
}
I am new to Swift but am trying to use a PHP based web service that returns a reply :-
{
responseData = {
1 = {
name = "3D SYSTEMS CORP";
result = success;
};
10 = {
name = "ABERCROMBIE & FITCH CO-CL A";
result = success;
};
};
}
I have assumed this is a valid JSON format but have not been able to work out how to load this data which is just a list of strings into an string array. I have trie code such as this to iterate one rte elements but with no success.
let url = urlComponents.URL
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {
(data, response, error) -> Void in
if let urlContnet = data
{
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContnet, options: NSJSONReadingOptions.MutableContainers)
if let nudges = jsonResult["responseData"] as? NSArray {
for nudge in nudges {
print(nudge)
}
}
}
catch
{
print("ERROR")
}
}
}
task.resume()
can anyone help
Thanks
Steve
There are two things here
Your JSON is malformed. You can check the validity of JSON at:
http://jsonlint.com
There are several issues in your JSON. The corrected JSON is here:
{
"responseData": {
"1": {
"name": "3D SYSTEMS CORP",
"result": "success"
},
"10": {
"name": "ABERCROMBIE & FITCH CO-CL A",
"result": "success"
}
}
}
responseData is a dictionary and not an array. Change your code to process this dictionary instead of array.
HTH
Using Alamofire and SwiftyJSON to retrieve some JSON is trivial:
Given JSON such as
{
"results": [
{
"id": "123",
"name": "Bob"
},
{
"id": "456",
"name": "Sally"
}
}
This function will work:
func loadSomeJSONData() {
Alamofire.request(.GET, "http://example.com/json/")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
if let firstName = json["results"][0]["name"].string {
println("first name: \(firstName)") // firstName will equal "Bob"
}
}
}
All well and good. My problem arises when I need to load JSON from a paged API, that is, when the data is collected from multiple calls to an API endpoint, where the JSON looks more like:
{
"currentPage": "1",
"totalPages": "6"
"results": [
{
"id": "123",
"name": "Bob"
},
{
"id": "456",
"name": "Sally"
}
]
}
and then the next block would look like:
{
"currentPage": "2",
"totalPages": "6"
"results": [
{
"id": "789",
"name": "Fred"
},
{
"id": "012",
"name": "Jane"
}
]
}
In this case, I can recursively call a function to gather all the "pages" but I'm not sure how to put all the JSON fragments together properly:
func loadSomeJSONDataFromPagedEndPoint(page : Int = 1) {
Alamofire.request(.GET, "http://example.com/json/" + page)
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
if let totalPages = json["totalPages"].description.toInt() {
if let currentPage = json["currentPage"].description.toInt() {
let pageOfJSON = json["results"]
// add pageOfJSON to allJSON somehow??
if currentPage < totalPages {
self.loadSomeJSONDataFromPagedEndPoint(page: currentPage+1)
} else {
// done loading all JSON pages
}
}
}
var allJSON
loadSomeJSONDataFromPagedEndPoint()
What I'd like to happen is to have the "results" portion of each JSON response eventually collected into a single array of objects (the { "id": "123", "name": "Bob"} objects)
Bonus question: I'm not sure why I need to do json["totalPages"].description.toInt() in order to get the value of totalPages, there must be a better way?
You have several questions in here, so let's take them one at a time.
I can't tell from your post if you get valid JSON back for each page call or whether you need to put them altogether to complete the JSON. So let's walk through both cases.
Option 1 - Valid JSON from each Page
You're already very close, you just need to tweak your JSON parsing a bit and store the results. Here's what this could look like.
class PagedDownloader {
var pagedResults = [AnyObject]()
func loadSomeJSONDataFromPagedEndPoint(page: Int) {
let request = Alamofire.request(.GET, "http://example.com/json/\(page)")
request.responseJSON { [weak self] _, _, jsonData, _ in
if let strongSelf = self {
let json = JSON(jsonData!)
let totalPages = json["totalPages"].stringValue.toInt()!
let currentPage = json["currentPage"].stringValue.toInt()!
let results = json["results"].arrayObject!
strongSelf.pagedResults += results
if currentPage < totalPages {
strongSelf.loadSomeJSONDataFromPagedEndPoint(currentPage + 1)
} else {
strongSelf.parsePagedResults()
}
}
}
}
func parsePagedResults() {
let json = JSON(pagedResults)
println(json)
}
}
You seem to know your way around SwiftyJSON so I'll let you handle the parsePagedResults implementation.
Option 2 - Pages must be assembled to create valid JSON
Paging JSON
First off, you can't parse partial JSON, it just won't work. The NSJSONSerialization will fail. This means that you can't use the responseJSON serializer with paged JSON because data will always be nil and error will always be the json serialization error. Long story short, you need cache all your data until it's valid JSON, then you can parse.
Storing Paged JSON
If you're going to store it, this is what it could look like as a simple example without Alamofire getting in the mix.
class Pager {
let page1 = "{\"currentPage\":\"1\",\"totalPages\":\"3\",\"results\":[{\"id\":\"123\",\"name\":\"Bob\"},"
let page2 = "{\"id\":\"456\",\"name\":\"Sally\"},{\"id\":\"234\",\"name\":\"Christian\"},"
let page3 = "{\"id\":\"567\",\"name\":\"Jerry\"},{\"id\":\"345\",\"name\":\"John\"}]}"
let pages: [String]
let jsonData: NSMutableData
init() {
self.pages = [page1, page2, page3]
self.jsonData = NSMutableData()
}
func downloadPages() {
for (index, page) in enumerate(pages) {
jsonData.appendData(page.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
}
let json = JSON(data: jsonData)
println(json)
if let totalPages = json["totalPages"].string?.toInt() {
println("Total Pages Value: \(totalPages)")
}
}
}
Your bonus question is answered at the end of that code chunk. You don't want to use description from SwiftyJSON, but instead the string optional cast and then optional chain into the toInt method.
Paging and Storing with Alamofire
Now that you have a simple example of how to write the JSON pages into data chunks, let's look at how that same approach could be used with the response serializer in Alamofire.
class Downloader {
var jsonData = NSMutableData()
var totalPagesDownloaded = 0
let totalPagesToDownload = 6
func loadSomeJSONDataFromPagedEndPoint() {
for page in 1...self.totalPagesToDownload {
let request = Alamofire.request(.GET, "http://example.com/json/\(page)")
request.response { [weak self] _, _, data, _ in
if let strongSelf = self {
strongSelf.jsonData.appendData(data as NSData)
++strongSelf.totalPagesDownloaded
if strongSelf.totalPagesDownloaded == strongSelf.totalPagesToDownload {
strongSelf.parseJSONData()
}
}
}
}
}
func parseJSONData() {
let json = JSON(data: jsonData)
println(json)
}
}
Parsing the Resulting JSON with SwiftyJSON
Inside the parseJSONData function, just use all the awesome features of SwiftyJSON to parse out the values you need.
I'm pretty sure that covers all your possible use cases and questions. Hope that helps!