The Jsend protocol is a simple 'standard' of how to format json responses in a REST API. https://github.com/omniti-labs/jsend
I am generating Swagger documentation using https://github.com/swaggo/swag but am having great trouble working out how to use the declarative comment format to describe Jsend responses.
If anyone has done this, I would greatly appreciate an example snippet of how they defined the jsend response using the swag declarative comment format.
I solved this by switching to goswagger.io which had easier to handle syntax. These are the models used for the basic jsend types. For other responses I replaced the Data element with the name of the relevent struct and swagger did the rest.
// Success: no response data required
// swagger:response responseSuccess
type responseSuccess struct {
// in: body
Body struct {
// enum: success
Status string `json:"status"`
Data interface{} `json:"data"`
} `json:"body"`
}
// Error: Incorrect use of the API or the requested data is not available
// swagger:response responseError
type responseError struct {
// in: body
Body struct {
// enum: error
Status string `json:"status"`
Data interface{} `json:"data"`
Message string `json:"message"`
} `json:"body"`
}
// Fail: Backend or system failure.
// swagger:response responseFail
type responseFail struct {
// in: body
Body struct {
// enum: fail
Status string `json:"status"`
Data interface{} `json:"data"`
Message string `json:"message"`
} `json:"body"`
}
Related
I am currently writing an Alamofire HTTP request and am running into an issue where my view is not loading - likely because there is no data. The confusing part is that this was working yesterday. In the request I was able to do print(data) and the result was 506 bytes which, if my calculation is correct, is about the correct size given the JSON payload returned from the endpoint below.
#State var recipes = [Recipe]()
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else { return }
if let response = try? JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
return
}
}
I can confirm that the endpoint that is being hit returns the following data...
[
{
"name":"Manhattan",
"image":"https://images.unsplash.com/photo-1536935338788-846bb9981813?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2486&q=80",
"spirit":"Bourbon",
"ice":"Crushed",
"glass":"Coupe",
"yield":"3.75",
"description":"This is a good drink. You should make it.",
"ingredients":[
{
"bottle":"High West Son Of Bourye",
"amount":"2.5"
},
{
"bottle":"Cocchi Vermouth Di Torino",
"amount":"0.75"
},
{
"bottle":"Simple Syrup",
"amount":"0.083"
}
]
}
]
I also have my Recipe and Ingredient model here which should be able to decode based on the above JSON.
struct Recipe: Decodable, Identifiable {
var id = UUID()
var name: String
var image: String
var spirit: String
var ice: String
var glass: String
var yield: String
var description: String
var ingredients: [Ingredient]
}
struct Ingredient: Decodable, Identifiable {
var id = UUID()
var bottle: String
var amount: String
}
Is anybody able to spot an issue? I was trying to put a debugging print in the DispatchQueue but it is not printing which, to me, sounds like an error. However I am new to Swift/XCode/iOS and am not sure the best debugging practices for this.
If you can't debug yourself, NEVER USE try?. With more experience, I'd say that we tend to not use try?, but sometimes we do. But when we write try?, we are able to find an possible issue, ie debug if needed.
Let's do a proper try then, with a do/catch:
do {
let response = try JSONDecoder().decode([Recipe].self, from: data
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
}
And read the output.
Going further?
Don't believe what's the API is supposed to return.
I've seen plenty and plenty of questions where the returned values was an error message, a XML Error message, a JSON Error message, an HTML Error message, and a JSON value missing, or of bad type, etc. And that, your JSONDecoder wasn't expecting it...
Reasons could be various, from bad/missing parameters, bad/missing APIKey, server down, bad/missing header, etc.
But, then, print the returned value.
print(String(data: data, encoding: .utf8) ?? "No data found")
So print it directly when you get it, or at least in the catch:
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
print("While getting response stringified: \(String(data: data, encoding: .utf8) ?? "No data found")")
}
If you don't understand the error message output, it's okay, there is no shame about it. But your first job is to get that error message. You can share it on SO if you don't understand it, you might get help with that. But currently, we can't guess what's wrong with your code.
It's a good idea to drop some clues in your code when looking for a failure.
If it were me I'd do something like this:
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else {
print("Error trying to receive data in ", #file, #function)
return
}
do {
let response = try JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Error failed to decode json data with error: \(error) in \(#file)", #function)
}
}
I want to know how can I force all the properties from this struct to be able to send a POST request to our API?
First of all. I need all those optional properties because I make a GET request, I receive all those documents, I process the data, I add the file property (which is an object) then I need to send all those documents back to our server with a file added.
We have our Document
struct Document: Codable {
let allowedFormats: [String]?
let processWhereApply: [String]?
let isRequired: Bool?
let id, key, title, description: String?
var file: File?
// More properties
}
But it fails every time, because I'm not sending a string for example. I'm sending an Optional<String>
Is there anyway possible I can "force" all those properties to send them back? without like writting 25 lines of guard let var = var else { return } ?
I'm sending the POST request with the parameters like this
let params = [
"userId": userId,
"groupId": groupId,
"fileDocuments": documents! //sends all properties optional
] as [String: Any]
Api().saveDocuments(params: params)
I'm assuming that you are sending the data back as Json. In that case just use the Json encode method to convert the struct to Json which can be sent as a POST request. Json encode will deal with the null value issue by setting a corresponding value to the key in your json if the value exists and not creating the key if it doesn't exist.
For example just to get the json:
let doc1 = Document(!here you will be initialising variables!)
// below gives you json as data
let json = try! JSONEncoder().encode(doc1)
// if you want your json as string
let str = String(decoding: json, as: UTF8.self)
Here is an example making an alamofire POST request. In the case of alamofire it automatically encodes your struct as long as it conforms to Codable:
let doc1 = Document(!here you will be initialising variables!)
AF.request("https://yoururl.com",method: .post,parameters: doc1, encoder: JSONParameterEncoder.default).responseJSON { response in
switch response.result {
case .success(let json):
print("good response")
break
case .failure(let error):
print("bad response"
}
}
I have some JSON response, that I take from a server. In success case, it might be like:
{
"success": true,
"data": [
{
/// something here
}
]
}
If all server responses would be successful, it would be really easy to parse that JSON. But we have also failure cases like:
{
"success": false,
"msg": "Your session expired",
"end_session": true
}
That means we need to handle two cases. As you noticed, attributes like success, msg may occur in any response. In order to handle that, I created following struct:
struct RegularResponse<T: Codable>: Codable {
let success: Bool
let msg: String?
let endSession: Bool?
let data: T?
enum CodingKeys: String, CodingKey {
case success, msg, data
case endSession = "end_session"
}
}
It may contain some data if response is successfull or otherwise, it is possible to identify why the error occurred(using success attribute or msg). Parsing process would go like following:
let model = try JSONDecoder().decode(RegularResponse<MyModel>.self, from: data)
if model.success {
// do something with data
} else {
// handle error
}
Everything works fine, but what if following JSON comes as following:
{
"success": true,
"name": "Jon Snow",
"living_place": "Nights Watch",
//some other fields
}
Here, I don't have data attribute. It means, my RegularResponse cannot be parsed. So, the question is how to handle these kind of situations? My idea for solution is simple: always put data in success cases into data field on my API. By doing so, my RegularResponse will always work, no matter what is inside data. But, it requires changes on a server side. Can this be fixed in a client side, not changing a server side? In other words, how to handle above situation in Swift using Codable?
I'm not sure if this is the best solution but if you know that your error response is in that shape, i.e.:
{
"success": false,
"msg": "Some error",
"end_session": "true",
}
then you could make another Codable struct/class that follows this response.
struct ErrorResponse: Codable {
let success: Bool
let msg: String
let end_session: String
}
and then when you are responding to your JSON you could adjust your code to:
if let successResponse = try? JSONDecoder().decode(RegularResponse<MyModel>.self, from: data) {
//handle success
} else if let responseError = try? JSONDecoder().decode(ErrorResponse.self, from data) {
//handle your error
}
I have an API client that uses generic API response that conforms to Codable Protocol and uses JSONDecoder to decode the response as shown below, how do I handle having a response which doesn't return JSON ( status code 201 created)?
dataRequest.validate().responseJSON { response in
if let error = response.error {
completion(.failure(error.localizedDescription))
} else if let data = response.data {
do {
let apiResponse = try JSONDecoder().decode(T.Response.self, from: data)
completion(.success(apiResponse))
} catch {
completion(.failure(error.localizedDescription))
}
} else {
completion(.failure("Something went wrong, please try again later."))
}
}
It returns this error:
the response could not be serialized input data was nil or zero-length
In this case you can look at the statusCode property of the response (assuming that it is a HTTPURLResponse) and make your determination about whether or not there will be a body to parse. I would put it immediately after the error check.
I am using fetch API to send two values to my POST request handler...
fetch('http://localhost:8080/validation', {
method:'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email:this.state.email,
password:this.state.password
})
I want to save both email and password as strings on the server side. Here is my attempt...
type credentials struct {
Test string
}
func Validate(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
decoder := json.NewDecoder(req.Body)
var creds credentials
err := decoder.Decode(&creds)
if err != nil {
panic(err)
}
fmt.Println(creds.Test)
}
The problem is I do not know how exactly the format of the structure being sent to the POST. I am attempting to save req.Body as a string but this yields nothing.
When I print fmt.Println I get a blank space. What is the proper way of parsing it?
Try with
type credentials struct {
Email string `json:"email"`
Password string `json:"password"`
}
You are receiving a JSON with two values. Receiving struct should have a structure matching your request. Otherwise, there are no placeholders to decode the JSON into, as in your case - email and password do not have matching struct fields. Btw. if you send "Test" in your JSON, this would work, as you have a Test field in your struct!
Regarding field names. If fields in JSON do not start with a capital letter or even have different names, then you should use so called tags.
More on tags: https://golang.org/pkg/encoding/json/#Marshal
In my example I used them to match struct field names to your json fields, i.e. to make email from json match Email field of the credentials struct.
req.Body is an io.Reader, and you can get use ioutil.ReadAll to drain it:
data, err := ioutil.ReadAll(req.Body)
asString := string(data) // you can convert to a string with a typecast
But I'm not sure if that's what you meant by trying to save req.Body as a string.
To parse the response into a data structure, you can unmarshal it into a variable of type *interface{}:
var creds interface{}
decoder.Decode(&creds)
And then examine the value:
fmt.Printf("%#v\n", creds)
Or perhaps using pp.Println(creds) which I find easier to read.
The creds variable will represent the JSON object found in the body, for your example input this will be a map[string]interface{} with two entries, presumably both of them strings. Something like:
map[string]interface{}{
"email": email_value,
"password": password_value,
}
and you check the values with:
email, ok := creds["email"].(string)
if ok {
// email will contain the value because creds["email"] passed the type check
fmt.Printf("submitted email is %#v\n", email)
} else {
// "email" property was a string (may be missing, may be something else)
}
The documentation of json.Unmarshal explains the semantics of how arbitrary JSON strings can be parsed without knowing their structure in advance in the discussion about unmarshalling to interface values.