Display data from POST request Swift - ios

New to swift and wanted to display some data from a post request each time a user presses a button.
Class and struct for Post request:
import Foundation
struct Response: Codable, Identifiable{
var id = UUID()
var model = String()
var choices = String()
var generatedtext: [GeneratedText]
}
struct GeneratedText: Codable{
var finish_reason = String()
var index = Int()
var logprobs = String()
var text = String()
}
class GPT3TextComepletion: ObservableObject{
#Published var response = [Response]()
func textCompletion(promptText:String, completion:#escaping ([Response]) -> () ){
let token = "redacted"
let url = URL(string: "redacted URL")!
// prepare json data
var json: [String: Any] = [
"temperature": 0.8,
"max_tokens": 64,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0,
"stop": ["\n\n"]]
json["prompt"] = promptText
let jsonData = try? JSONSerialization.data(withJSONObject: json)
// create post request
var request = URLRequest(url: url)
request.httpMethod = "POST"
// insert json data to the request
request.httpBody = jsonData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
//TODO: Remove this once i get the response to display
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
let response = try! JSONDecoder().decode([Response].self, from: data)
DispatchQueue.main.async {
completion(response)
}
}
task.resume()
}
}
View:
Removed the unnecessary styling and other code from the below view:
#State var prompt: String = "This is some editable text..."
#State var response = [Response]()
utton(action: {GPT3TextComepletion().textCompletion(promptText: prompt, completion: {(response) in self.response = response })})
List (response){ response in
VStack{
Text(response.model)}}
However upon running the emulator and attempting the post call there seems to be an issue with how i am decoding the data and most likely how i have organized my structs, error message:
"Expected to decode Array but found a dictionary instead."
JSON:
["id": cmpl-4PCJZrxEm8YlCwFSTZlfLpnAUcMPl, "created": 1641910885, "object": text_completion, "model": davinci:2020-05-03, "choices": <__NSSingleObjectArrayI 0x6000012f1dc0>(
{
"finish_reason" = stop;
index = 0;
logprobs = "<null>";
text = "";
}
)
]
Obviously i am doing something wrong with how i am defining my struct however I have tried a number of different things with no success over the last few days.
Two main questions:
Is there an alternate way to display response of the post request without using List and making the struct identifiable?
What I am doing wrong with my structs here?
Thanks!

Related

iOS Swift SwiftUI - request json object from POST API

I'm new to iOS development
I need to make an API request sending some POST values and receiving json object
Ofc I have searched for tutorials and have seen other questions but all the codes I've found are causing all kind of errors.
This is what I've tried last:
func getAppConfig() async {
guard let url = URL(string:"https://blasrv.com/appconfig.php")
else{
return }
let body: [String: String] = ["userid": "420", "device": "ios"]
let finalBody = try? JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request){
(data, response, error) in
guard let data = data else{
return
}
do{
let jsondata = Data(data)
if let json = try JSONSerialization.jsonObject(with: jsondata, options: []) as? [String: Any] {
// try to read out a string array
if let nickname = json["nickname"] as? [String] {
print(nickname)
}
}
gotConfig = true
await fetchData()
}catch{
print("data not valid")
}
}
.resume()
}
It gives:
Cannot pass function of type '(Data?, URLResponse?, Error?) async -> Void' to parameter expecting synchronous function type
on
URLSession.shared.dataTask(with: request)
The problem is that you mix usage of old asynchronous way with new async wait way , you need
class ViewController: UIViewController {
var gotConfig = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Task {
do {
try await getAppConfig()
}
catch {
print(error)
}
}
}
func getAppConfig() async throws {
guard let url = URL(string:"https://blasrv.com/appconfig.php") else { return }
let body: [String: String] = ["userid": "420", "device": "ios"]
let finalBody = try JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let (data, _) = try await URLSession.shared.data(for: request)
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
// try to read out a string array
if let nickname = json["nickname"] as? [String] {
print(nickname)
}
}
gotConfig = true
await fetchData()
}
func fetchData() async {
}
}

Exception 'Invalid type in JSON write (__SwiftValue)' while JSON encoding Swift objects with arrays

Getting exception *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)' while trying to encode this Swift object to JSON. All non-optional members, objects Codable. What is the right way to encode or should use some 3rd party library?
struct MediaItem: Codable {
var key: String = ""
var filename: String = ""
}
struct NoteTask: Codable {
var id: String = ""
var notes: String = ""
var mediaList: [MediaItem] = []
}
static func addTask(task: NoteTask, callback: #escaping TaskAPICallback) {
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let url = URL(string: postUrl)
var request : URLRequest = URLRequest(url: url!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let params: [String: Codable?] = [
"email": task.id,
"notes": task.notes,
"fileList": task.fileList
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params, options: [])
} catch {
DispatchQueue.main.async {
callback(false)
}
return
}
...
}
The issue is that you're using JSONSerialization instead of JSONEncoder. JSONSerialization is the older, Foundation/Objective-C way of writing objects to JSON. It will only work with Foundation objects (see the documentation for a complete list).
Instead, you should use JSONEncoder. The tricky part is that JSONEncoder can't directly encode a Dictionary without some work on your part. There are a few ways to solve this, but if this is the only JSON format you're going to use, I'd probably just create custom keys for your structs using CodingKeys.
struct MediaItem: Codable {
var key: String = ""
var filename: String = ""
}
struct NoteTask: Codable {
var id: String = ""
var notes: String = ""
var mediaList: [MediaItem] = []
enum CodingKeys: String, CodingKey {
case id = "email"
case notes = "notes"
case mediaList = "fileList"
}
}
static func addTask(task: NoteTask, callback: #escaping TaskAPICallback) {
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let url = URL(string: postUrl)
var request : URLRequest = URLRequest(url: url!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
do {
request.httpBody = try JSONEncoder().encode(task)
} catch {
DispatchQueue.main.async {
callback(false)
}
return
}
}

Append multiple data to httpbody of URLRequest

I want to append another dictionary as a parameter to httpBody of URLRequest
Request Model:
struct RequestModel: Encodable {
let body: Body
}
struct Body: Encodable {
let name: String
let score: String
let favList: [String]
}
Api Request:
do {
var urlRequest = URLRequest(url: resourceURL)
urlRequest.httpMethod = kHTTPMethodPOST
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = try JSONEncoder().encode(self.requestModel)
let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let jsonData = data else {
completion(.failure(.responseError))
return
}
}
dataTask.resume()
} catch {
completion(.failure(.unknownError))
}
Another dictionary: airports
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
trying to append airports dictionary parameter to URLRequest but can't append.
Appreciate your help and suggestion!
Thanks
If you have to append the airports dictionary to the request body, you may want to include it in the Request model itself.
I would suggest updating your RequestModel and make it Encodable.
And include your airports dict as a part of your RequestModel
Something like this
struct RequestModel: Encodable {
let body: Body
let airportsDict: [String:String]
}
struct Body: Encodable {
let name: String
let score: String
let favList: [String]
}
This way your httpBody will have all the data you want to pass.
Hope this helps
The usual syntax to POST JSON is
do {
var urlRequest = URLRequest(url: resourceURL)
urlRequest.httpMethod = "POST"
let postData = try JSONEncoder().encode(self.requestModel)
urlRequest.httpBody = postData
urlRequest.setValue("\(postData.count)", forHTTPHeaderField:"Content-Length")
urlRequest.setValue("application/json", forHTTPHeaderField:"Accept")
urlRequest.setValue("application/json", forHTTPHeaderField:"Content-Type")
}

Decodable Json, function doesn't run at all?

I know this decodable json question has been asked a lot of times, in-fact i was able to do the retreival of data for one of my other projects. However, I cant seem to get this to work. IT is suppose to retrieve the data, decode it and store it. However, my print function was never ran. I don't see the "executed" print and moreover. It does print httpResponse with the headers from the api so I know it is working and it has a response.
Decodable
struct GetId: Decodable {
let id : String?
let deck_id : String?
var completed_at : String?
let created_at : String?
let locale_key : String?
let profile_id : String?
let recommendation_id : String
let package_id : String?
let status : String?
let scoring_scale : String?
}
Sample Response
{
"id": "XXXXXXX-XXX-4c6a-XXXX-1XXXXXXX223",
"deck_id": "career-deck",
"completed_at": null,
"created_at": 1551867228744,
"locale_key": "en-US",
"profile_id": "XXXXXXX-XXX-4c6a-XXXX-1XXXXXXX223",
"recommendation_id": null,
"package_id": null,
"status": "created",
"scoring_scale": "NORMAL"
}
My function
func requestId()
{
let headers = [
"Authorization": "Basic XXXxxxxXXxxXXXXxxxXXX:x",
"Content-Type": "application/json",
"cache-control": "no-cache"
]
let parameters = ["deck_id": "career-deck"] as [String : Any]
let postData = try? JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://api.traitify.com/v1/assessments")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = (postData as! Data)
//let session = URLSession.shared
//let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
if let getIdd = try? JSONDecoder().decode([GetId].self, from: data!){
getId = getIdd
print(getId[0].id)
print("executed")
}
}
}
task.resume()
}
From your example, created_at is not a string (it is a numeric value) and recommendation_id needs to be optional (like all the other fields).
Also, your example is a single record, but you are decoding an array of objects. Change the decode to be JSONDecoder().decode(GetId.self, from: data!)

Get Method JSON Response(String) Coding Complaint for key

My ResponseString is as follows,
SUCCESS:
{"code":200,"shop_detail":{"name":"dad","address":"556666"},
"shop_types : [{"name":"IT\/SOFTWARE","merchant_type":"office"}]}
My Get request code with headers is as follows,
func getProfileAPI() {
let headers: HTTPHeaders = [
"Authorisation": AuthService.instance.tokenId ?? "",
"Content-Type": "application/json",
"Accept": "application/json"
]
print(headers)
let scriptUrl = "http://haitch.igenuz.com/api/merchant/profile"
if let url = URL(string: scriptUrl) {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = HTTPMethod.get.rawValue
urlRequest.addValue(AuthService.instance.tokenId ?? "", forHTTPHeaderField: "Authorization")
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
Alamofire.request(urlRequest)
.responseString { response in
debugPrint(response)
print(response)
if let result = response.result.value // getting the json value from the server
{
print(result)
let jsonData1 = result as NSString
print(jsonData1)
let name = jsonData1.object(forKey: "code") as! [AnyHashable: Any]
print(name)
// var data = jsonData1!["shop_detail"]?["name"] as? String
} }
}
When I tried to get the value for "name" its getting'[<__NSCFString 0x7b40f400> valueForUndefinedKey:]: this class is not key value coding-compliant for the key code. Please guide me to get the values of name, address..?????
You can use the Response Handler instead of Response String Handler:
Response Handler
The response handler does NOT evaluate any of the response data. It
merely forwards on all information directly from the URL session
delegate. It is the Alamofire equivalent of using cURL to execute a Request.
struct Root: Codable {
let code: Int
let shopDetail: ShopDetail
let shopTypes: [ShopType]
}
struct ShopDetail: Codable {
let name, address: String
}
struct ShopType: Codable {
let name, merchantType: String
}
Also you can omit the coding keys from your struct declaration if you set your decoder keyDecodingStrategy (check this) to .convertFromSnakeCase as already mentioned in comments by #vadian:
Alamofire.request(urlRequest).response { response in
guard
let data = response.data,
let json = String(data: data, encoding: .utf8)
else { return }
print("json:", json)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let root = try decoder.decode(Root.self, from: data)
print(root.shopDetail.name)
print(root.shopDetail.address)
for shop in root.shopTypes {
print(shop.name)
print(shop.merchantType)
}
} catch {
print(error)
}
}
For more information about encoding and decoding custom types you can read this post.
You can try to convert the json string to data then decode it
struct Root: Codable {
let code: Int
let shopDetail: ShopDetail
let shopTypes: [ShopType]
}
struct ShopDetail: Codable {
let name, address: String
}
struct ShopType: Codable {
let name, merchantType: String
}
Then
let jsonStr = result as! String
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let res = try? dec.decode(Root.self,from:jsonStr.data(using:.utf8)!)
Note your str json may be invalid as you miss " after shop_types so make sure it looks like this
{"code":200,"shop_detail":{"name":"dad","address":"556666"},
"shop_types" : [{"name":"IT/SOFTWARE","merchant_type":"office"}]}

Resources