How to post this JSON data as HTTP body using URLSession? - json

The JSON I have to post:
{
"quizId": 1,
"quizQuestionBanks": [
{
"quizQuestionBankId": 4,
"question": "string",
"option1": "string",
"option2": "string",
"option3": "string",
"option4": "string",
"answer": "Guy de Maupassant",
"explanation": "string"
}
]
}
I did the URLSession Post part. But don't know how to post this kind of JSON. Earlier I have posted JSON like this.
let json: [String: Any] = ["key": "value"]
But it's a bit complex for me.
The code I did for posting is given below
let url = URL(string: postUrl)! //PUT Your URL
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("\(String(describing: jsonData?.count))", forHTTPHeaderField: "Content-Length")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Token \(String(describing: token))", forHTTPHeaderField: "Authorization")
// insert json data to the request
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON) //Code after Successfull POST Request
}
}.resume()

Prepare JSON data for upload
struct Quiz: Codable {
let quizId: Int
let quizQuestionBanks: [QuizQuestionBank]
}
struct QuizQuestionBank: Codable {
let quizQuestionBankId: Int
let question: String
let option1: String
let option2: String
let option3: String
let option4: String
let answer: String
let explanation: String
}
let quiz = Quiz(quizId: 1, quizQuestionBanks: [QuizQuestionBank(quizQuestionBankId: 4, question: "string", option1: "string", option2: "string", option3: "string", option4: "string", answer: "Guy de Maupassant", explanation: "string")])
guard let uploadData = try? JSONEncoder().encode(quiz) else {
return
}
Configure URL request
let url = URL(string: "https://example.com/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
Use upload task
let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
if let error = error {
print ("error: \(error)")
return
}
guard let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode) else {
print ("server error")
return
}
if let mimeType = response.mimeType,
mimeType == "application/json",
let data = data,
let dataString = String(data: data, encoding: .utf8) {
print ("got data: \(dataString)")
}
}
task.resume()
See this link from Apple developer site for more detailed information.
https://developer.apple.com/documentation/foundation/url_loading_system/uploading_data_to_a_website

With JSONSerialization:
let params: [String: Any] = ["quizId": 1,
"quizQuestionBanks": [["quizQuestionBankId": 4,
"question": "string",
"option1": "string",
"option2": "string",
"option3": "string",
"option4": "string",
"answer": "Guy de Maupassant",
"explanation": "string"]]]
let jsonData = try? JSONSerialization.data(withJSONObject: params)
But, since Swift 4, we can use Codable:
struct Quiz: Codable {
let id: Int
let questions: [Question]
enum CodingKeys: String, CodingKey {
case id = "quizId"
case questions = "quizQuestionBanks"
}
}
struct Question: Codable {
let id: Int
let question: String
let option1: String
let option2: String
let option3: String
let option4: String
let answer: String
let explanation: String
enum CodingKeys: String, CodingKey {
case id = "quizQuestionBankId"
case question
case option1
case option2
case option3
case option4
case answer
case explanation
}
}
let questions = [Question(id: 4, question: "string", option1: "string", option2: "string", option3: "string", option4: "string", answer: "Guy de Maupassant", explanation: "string")]
let quiz = Quiz(id: 1, questions: questions)
let jsonData = try JSONEncoder().encode(quiz)
option1, option2, option3, option4, could be an array in struct (that would need a custom encoding/decoding).

Related

How to decode a JSON Dictionary?

I am able to decode straight forward json responses but as the JSON gets more nested, I'm struggling.
To decode JSON that looks like this:
[
{
"id": "string",
"username": "string",
"firstName": "string",
"lastName": "string",
"fullName": "string",
"email": "string",
"isInActivity": true,
"activeEventId": "string",
"initials": "string"
}
]
My struct is:
struct FriendsStruct: Decodable, Hashable {
var initials: String
var username: String
var firstName: String
var lastName: String
var fullName: String
var email: String
var isInActivity: Bool
var activeEventId: String
var id: String
}
And to decode:
func getFriends(token: String, force: Bool) async throws -> Int {
var request = EndPoints().getFriendsEndPoint(force: force)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
let httpResponse = response as? HTTPURLResponse
guard (response as? HTTPURLResponse)?.statusCode == 200 else {return httpResponse?.statusCode ?? 0}
let decodedFriends = try JSONDecoder().decode([FriendsStruct].self, from: data)
self.updateCoreDataFriendsRecords(friendsData: decodedFriends)
return httpResponse?.statusCode ?? 1000
}
Can someone advise as to how I might decode with the same approach but with a nested response such as:
{
"members": [
{
"memberId": "string",
"memberName": "string",
"memberUsername": "string",
"memberType": 0,
"isMyFriend": true,
"initials": "string"
}
],
"name": "string",
"owner": "string",
"description": "string",
"groupType": 0,
"expiryDate": "2023-02-06T20:00:03.834Z",
"readOnly": true,
"isDeleted": true,
"approvalRequired": true,
"joinWithCode": true,
"numberOfMembers": 0,
"groupAssociation": 0,
"id": "string",
"etag": "string"
}
You need two structs: a Member struct with properties in the JSON (as you did for the simple example) and an outer level struct for the base data that includes a property which is an array of Member. For example:
struct Member: Decodable {
let memberId: String
let memberName: String
let memberUsername: String
let memberType: Int
let isMyFriend: Bool
let initials: String
}
struct Group: Decodable {
let members: [Member]
let name: String
let owner: String
let description: String
let groupType: Int
let expiryDate: String
let readOnly: Bool
let isDeleted: Bool
let approvalRequired: Bool
let joinWithCode: Bool
let numberOfMembers: Int
let groupAssociation: Int
let id: String
let etag: String
}
This is treating the data exactly as it is in the JSON. You'd probably want to go further and use a CodingKeys enum to map some of the json fields onto more suitable property names, and maybe, depending on needs, use a Date for the expiry date and decode the date string.
EDIT
As a follow up I mentioned the next step might be decoding the expiryDate field into a Date property. The answer passed over this as it appeared to be an .iso8601 date format, in which case all that is required is to set the date decoding strategy on the decoder accordingly:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601)
let group = decoder.decode(Group.self, from: json.data(using: .utf8)!) // force unwrapping for brevity. Don't ;-)
However this won't work as the date field contains fractional seconds and Swift's decoder only supports whole seconds. This makes it a bit more interesting :-) as you'll need to define a custom decoder:
extension DateFormatter {
static let iso8601WithFractionalSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}
This then lets you decoder the expiryDate string to a Date within the decoder. Change the expiry date field to a Date and decode as below.
struct Group: Decodable {
//...
let expiryDate: Date
//...
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601WithFractionalSeconds)
let group = try! decoder.decode(Group.self, from: data)

Parsing nested JSON using Decodable in Swift

I'm trying to parse this JSON response
{
"payload": {
"bgl_category": [{
"number": "X",
"name": "",
"parent_number": null,
"id": 48488,
"description": "Baustellenunterk\u00fcnfte, Container",
"children_count": 6
}, {
"number": "Y",
"name": "",
"parent_number": null,
"id": 49586,
"description": "Ger\u00e4te f\u00fcr Vermessung, Labor, B\u00fcro, Kommunikation, \u00dcberwachung, K\u00fcche",
"children_count": 7
}]
},
"meta": {
"total": 21
}
}
What I'm interested to view in my TableViewCell are only the number and description
here is what I tried to far:
//MARK: - BGLCats
struct BGLCats: Decodable {
let meta : Meta!
let payload : Payload!
}
//MARK: - Payload
struct Payload: Decodable {
let bglCategory : [BglCategory]!
}
//MARK: - BglCategory
struct BglCategory: Decodable {
let descriptionField : String
let id : Int
let name : String
let number : String
let parentNumber : Int
}
//MARK: - Meta
struct Meta: Decodable {
let total : Int
}
API request:
fileprivate func getBgls() {
guard let authToken = getAuthToken() else {
return
}
let headers = [
"content-type" : "application/json",
"cache-control": "no-cache",
"Accept" : "application/json",
"Authorization": "\(authToken)"
]
let request = NSMutableURLRequest(url: NSURL(string: "https://api-dev.com")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
request.allHTTPHeaderFields = headers
let endpoint = "https://api-dev.com"
guard let url = URL(string: endpoint) else { return }
URLSession.shared.dataTask(with: request as URLRequest) {(data, response, error) in
guard let data = data else { return }
do {
let BGLList = try JSONDecoder().decode(BglCategory.self, from: data)
print(BGLList)
DispatchQueue.main.sync { [ weak self] in
self?.number = BGLList.number
self?.desc = BGLList.descriptionField
// self?.id = BGLList.id
print("Number: \(self?.number ?? "Unknown" )")
print("desc: \(self?.desc ?? "Unknown" )")
// print("id: \(self?.id ?? 0 )")
}
} catch let jsonError {
print("Error Serializing JSON:", jsonError)
}
}.resume()
}
But I'm getting error:
Error Serializing JSON: keyNotFound(CodingKeys(stringValue: "childrenCount", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"childrenCount\", intValue: nil) (\"childrenCount\").", underlyingError: nil))
There are a few issues here.
You created the model (mostly) correctly, but there're just two mismatches:
struct BglCategory: Decodable {
let description : String // renamed, to match "description" in JSON
let parentNum: Int? // optional, because some values are null
// ...
}
Second issue is that your model properties are camelCased whereas JSON is snake_cased. JSONDecoder has a .convertFromSnakeCase startegy to automatically handle that. You need to set it on the decoder prior to decoding.
Third issue is that you need to decode the root object BGLCats, instead of BglCategory.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // set the decoding strategy
let bglCats = try decoder.decode(BGLCats.self, from: data) // decode BGLCats
let blgCategories = bglCats.payload.bglCategory
The problem is that JSONDecoder doesn't know that for example bglCategory is represented in JSON payload as bgl_category. If the JSON name isn't the same as the variable name you need to implement CodingKeys to your Decodable
In your case:
struct BglCategory: Decodable {
let descriptionField : String
let id : Int
let name : String
let number : String
let parentNumber : Int?
enum CodingKeys: String, CodingKey {
case id, name, number
case descriptionField = "description"
case parentNumber = "parent_number"
}
}
struct Payload: Decodable {
let bglCategory : [BglCategory]!
enum CodingKeys: String, CodingKey {
case bglCategory = "bgl_category"
}
}

PUT request in iOS (Swift) with JSONSerialization

I'm trying to update an attribute via PUT request using JSONSerialization.
Here is my function :
func updateRGPDStatus(user:UserModel){
let parameters = ["firstname": user.firsname as Any,
"lastname": user.lastname as Any,
"mail": user.mail as Any,
"has_web_access": user.has_web_access as Any,
"has_mobile_access": user.has_mobile_access as Any,
"id_user": user.id_user as Any,
"has_accepted_rgpd": user.rgpd as Any
] as [String : Any]
guard let url = URL(string: UPDATE_RGPD_STATUS) else { return }
var request = URLRequest(url: url)
request.httpMethod = "PUT"
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else {
return
}
request.httpBody = httpBody
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
}
catch {
print(error)
}
}
}.resume()
}
and here is the json that I'm supposed to update :
{
"user": {
"firstname": "first_name",
"lastname": "last_name",
"mail": "validEmail#mail.com",
"has_web_access": true,
"has_mobile_access": true,
"id_user": 17,
"has_accepted_rgpd": false
}
}
When I send my request, I got and error of type "missing parameters" because I don't include the "user" attribute in my request which I don't know how to do it at this stage of my learning. Any help please. Thank you ^^
You can try
let res:[String : Any] = ["firstname": user.firsname as Any,
"lastname": user.lastname as Any,
"mail": user.mail as Any,
"has_web_access": user.has_web_access as Any,
"has_mobile_access": user.has_mobile_access as Any,
"id_user": user.id_user as Any,
"has_accepted_rgpd": user.rgpd as Any
]
let parameters:[String : Any] = ["user":res] // the new parameters
I have recently worked with the http put method i sent parameters like this
let param = [
"quantity" : quantity!
] as [String : Any]
And this worked for me.

Swift is not assigning variables from JSON results

I have an issue with loading JSON results within swift (php connection).
I can retrieve JSON data but it will not let me assign it to a variable.
it always assigns the results as Optional.
The JSON Data:
{
"country": [{
"id": 1,
"name": "Australia",
"code": 61
}, {
"id": 2,
"name": "New Zealand",
"code": 64
}]
}
The xCode Output:
["country": <__NSArrayI 0x60000002da20>(
{
code = 61;
id = 1;
name = Australia;
},
{
code = 64;
id = 2;
name = "New Zealand";
}
)
]
Country Name: Optional(Australia)
Country Name: Optional(New Zealand)
The .swift file:
//function did_load
override func viewDidLoad() {
super.viewDidLoad()
//created RequestURL
let requestURL = URL(string: get_codes)
//creating NSMutable
let request = NSMutableURLRequest(url: requestURL!)
//setting the method to GET
request.httpMethod = "GET"
//create a task to get results
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//lets parse the response
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: Any]
print(json)
if let countries = json["country"] as? [[String: AnyObject]] {
for country in countries {
print("Country Name: \(String(describing: country["name"]))")
print("Country Code: \(String(describing: country["code"]))")
if let couname = country["name"] as? [AnyObject] {
print(couname)
}
if let coucode = country["code"] as? [AnyObject] {
print(coucode)
}
}
}
} catch {
print("Error Serializing JSON: \(error)")
}
}
//executing the task
task.resume()
}
You need to unwrap the optional before you try to use it via string interpolation. The safest way to do that is via optional binding:
Please use below code, which will work for you.
if let countries = json["country"] as? [[String: AnyObject]] {
for country in countries {
print("Country Name: \(country["name"] as! String)")
print("Country Code: \(country["code"] as! String)")
if let couname = country["name"] as? String {
print(couname)
}
if let coucode = country["code"] as? Int {
print(coucode)
}
}
}

How to create a JSON from Dictionary with an array

I have an object "itensList", it has the fields "name", "createdAt" and an array of "itens".
I want to be able to build JSON that looks like this:
{
"name": "List name"
"CreatedAt": "12:12 12/12/2016"
"itens": [
{
"title": "Item title"
"CreatedAt": "12:13 12/12/2016"
"isDone": false
},
{
"title": "Another item title"
"CreatedAt": "12:14 12/12/2016"
"isDone": true
}
]
}
I have tried a few different approaches with no success.
Item Object
class Item: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
dynamic var isDone = false
}
Item List Object
class ItemList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let itens = List<Item>()
}
For the example, let's make an object similar to what you must have:
class Iten {
let title:String
let createdAt:String
let isDone:Bool
init(title: String, createdAt: String, isDone: Bool) {
self.title = title
self.createdAt = createdAt
self.isDone = isDone
}
}
The trick I suggest is to add a computed value that will return a dictionary:
class Iten {
let title:String
let createdAt:String
let isDone:Bool
init(title: String, createdAt: String, isDone: Bool) {
self.title = title
self.createdAt = createdAt
self.isDone = isDone
}
var toDictionary: [String:AnyObject] {
return ["title": title, "createdAt": createdAt, "isDone": isDone]
}
}
Let's use it:
let iten1Dict = Iten(title: "title1", createdAt: "date1", isDone: false).toDictionary
let iten2Dict = Iten(title: "title2", createdAt: "date2", isDone: true).toDictionary
We now make the encapsulating dictionary:
let dict: [String:AnyObject] = ["name": "List name", "createdAt": "dateX", "itens": [iten1Dict, iten2Dict]]
To finish, we encode this dictionary to JSON data then we decode it as a String:
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(dict, options: .PrettyPrinted)
if let jsonString = String(data: jsonData, encoding: NSUTF8StringEncoding) {
print(jsonString)
}
} catch let error as NSError {
print(error)
}
And voilĂ :
{
"createdAt" : "dateX",
"itens" : [
{
"title" : "title1",
"createdAt" : "date1",
"isDone" : false
},
{
"title" : "title2",
"createdAt" : "date2",
"isDone" : true
}
],
"name" : "List name"
}
Raphael,
This piece of code builds a JSON query. It should get you started, just keep hacking and you'll find a way! That's the fun of programming!
func JSONquery()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.dropboxapi.com/2/files/get_metadata")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json",forHTTPHeaderField: "Content-Type")
request.addValue("path", forHTTPHeaderField: lePath)
let cursor:NSDictionary? = ["path":lePath]
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(cursor!, options: [])
request.HTTPBody = jsonData
print("json ",jsonData)
} catch {
print("snafoo alert")
}
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if let error = error {
completion(string: nil, error: error)
return
}
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
//print("Body: \(strData)\n\n")
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers);
self.jsonPParser(jsonResult,field2file: "ignore")
/*for (key, value) in self.parsedJson {
print("key2 \(key) value2 \(value)")
}*/
completion(string: "", error: nil)
} catch {
completion(string: nil, error: error)
}
})
task.resume()
}
Like this:
var item = [
"title": "Item title",
"CreatedAt": "12:13 12/12/2016",
"isDone": false
]
var mainDictionary = [
"name": "List name",
"CreatedAt": "12:12 12/12/2016",
"items": [item]
]
And the just convert to json with NSJSONSerialization like this:
do {
let json = try NSJSONSerialization.dataWithJSONObject(mainDictionary, options: [])
} catch {
print(error)
}
UPDATE:
If you need to add values to array in dictionary you can do that like this:
if var items = mainDictionary["items"] as? NSMutableArray {
items.addObject(newItem)
mainDictionary["items"] = items
}