With swifter json response - swift

I am create simple rest api with swift using swifter library
How i can response json data?
import Swifter
let server = HttpServer()
server["/hello"] = { request in
var userList:[Any] = []
for user in users {
var b : [String: Any] = [:]
b["name"] = user.name
b["id"] = user.id
userList.append(b)
}
return .ok(.json(userList))
}
But there is below error message
Serialization error: invalidObject
I check the library source code, and found the error message reason
...
//Library Source code
func content() -> (Int, ((HttpResponseBodyWriter) throws -> Void)?) {
do {
switch self {
case .json(let object):
guard JSONSerialization.isValidJSONObject(object) else {
throw SerializationError.invalidObject
}
let data = try JSONSerialization.data(withJSONObject: object)
return (data.count, {
try $0.write(data)
})
...
So, I need pass guard JSONSerialization.isValidJSONObject(object) else {
also, there is no enough document for the library, How I can fix this problem ?

Use codable and a jsonEncoder to convert the users array to data and then convert them back to a jsonObject and pass it in:
do {
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(users)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
return .ok(.json(jsonObject))
} catch {
print(error)
return .internalServerError(.htmlBody("\(error.localizedDescription)"))
}
Note that you should consider returning an error to the caller of the service. I've used .internalServerError but you may consider returning a better error.

Related

Failed to decode data coming from client

I am following Ray Wanderlich's book 'Server Side Swift with Vapor' and I am at chapter 26: Adding profile pictures.
First, I defined this struct:
struct ImageUploadData: Content {
var picture: Data
}
Then, in a route I try to decode it:
func postProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<User> {
let data = try req.content.decode(ImageUploadData.self)
...
From the client side, I use Alamofire:
#discardableResult func uploadProfilePicture(for user: User, data: Data) async throws -> User {
enum Error: LocalizedError {
case missingUserID
}
guard let userID = user.id else {
throw Error.missingUserID
}
let appendix = "\(userID)/profilePicture"
let parameters = [
"picture": data
]
return try await withCheckedThrowingContinuation { continuation in
Task {
AF.request(baseUrl + appendix, method: .post, parameters: parameters).responseData { response in
switch response.result {
case .success(let data):
do {
let user = try JSONDecoder().decode(User.self, from: data)
continuation.resume(returning: user)
} catch {
continuation.resume(throwing: error)
}
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
}
In my integration tests, I create the picture's data like this:
guard let data = image?.pngData() else {
throw Error.missingPictureData
}
And then I pass it to the above method. The problem is that in the server side, the decoding fails with this error:
The data couldn’t be read because it is missing.
Just to understand if I was doing something else wrong, I tried the above methods with one difference: I replace the type 'Data' with 'String':
struct ImageUploadData: Content {
var picture: String
}
This wouldn't be useful for me because I need a data object, but just as a test to see if this doesn't produce an error, I tried and indeed this is decoded successfully. So I suspect that the problem is in how I encode the data before sending it to the server, but I don't know what's wrong.

Swift: Decode message sent from GameKit

I'm trying to receive messages in GameKit. The part of receiving the messages works well, but I can not figure out how to decode the data properly.
enum MessageType: Int, Codable {
case BestHost, GameBegin, YourTurn, PlayCard, GameOver
}
struct Message: Codable {
let messageType: MessageType
}
struct MessageBestHost: Codable {
let message: Message
let bestHostId: String
let bestHostName: String
}
I use the above infrastructure for sending and receiving my messages. For sending I encode the message as follows and then send it to all players:
func encode<T: Encodable>(_ item: T) throws -> Data {
let encoder = JSONEncoder()
return try encoder.encode(item)
}
func sendBestHost(player: GKPlayer) {
let message = MessageBestHost(message: Message(messageType: MessageType.BestHost), bestHostId: player.gamePlayerID, bestHostName: player.alias)
do {
try sendDataToAllPlayers(data: encode(message))
}
catch {
print("Error: \(error.localizedDescription)")
}
}
When receiving I use this method to decode the data:
func decode<T: Decodable>(from data:Data) throws -> T {
let decoder = JSONDecoder()
let item = try decoder.decode(T.self, from: data)
return item
}
let message: Message
do {
message = try decode (from: data)
print(message)
} catch {
print (error)
}
if message.messageType == MessageType.BestHost {
do {
let messageBestHost: MessageBestHost = try decode(from: data)
print("\(messageBestHost.bestHostName) hosts the game")
} catch {
print(error)
}
The problem now is. I first need to decode the message as type: Message in order to filter for the correct Subtype and do the magic for the referencing messageType. When trying to cast the data into a variable of type message, the decoding (obviously) fails because the sent data does not contain a messageType in the top hierarchical level.
Error: No value associated with key CodingKeys(stringValue: \"messageType\", intValue: nil)
Without being able to filter for the messageType first I won't be able to distinguish between different messages and execute different methods.
I guess the solution might lay within my data infrastructure but I cant think of any way to make this work. Does anybody have a clue how to solve this problem?
I might try something like this:
import Foundation
enum MessageType: Int, Codable {
case BestHost, GameBegin, YourTurn, PlayCard, GameOver
}
struct Message: Codable {
let messageType: MessageType
let data: Data
}
struct MessageBestHost: Codable {
let bestHostId: String
let bestHostName: String
}
do {
// Serialize:
let messageBestHost = MessageBestHost(bestHostId: "id", bestHostName: "name")
let messageBestHostData = try JSONEncoder().encode(messageBestHost)
let message = Message(messageType: .BestHost, data: messageBestHostData)
let messageData = try JSONEncoder().encode(message)
try sendDataToAllPlayers(data: encode(message))
// Deserialize:
let messageReceived = try JSONDecoder().decode(Message.self, from: messageData)
if messageReceived.messageType == .BestHost {
let messageBestHostReceived = try JSONDecoder().decode(MessageBestHost.self, from: messageReceived.data)
print(messageBestHostReceived.bestHostId)
print(messageBestHostReceived.bestHostName)
}
} catch {
print("Error: \(error.localizedDescription)")
}
Also swift enum cases should start with lowercase letter

Parse responseJSON to ObjectMapper

I'm currently making a migration from Android to iOS, better said Java to Swift, I got a generic response in JSON, but I'm not able to use it as an object and show it in the storyboard. I'm really new to Swift so I've been stuck for a while.
I've tried ObjectMapper and also JSON decode with no result at all.
I declared this response as I used in Java(Android)
class ResponseObjectMapper<T,R>: Mappable where T: Mappable,R:Mappable{
var data:T?
var message:String!
var error:R?
required init?(_ map: Map) {
self.mapping(map: map)
}
func mapping(map: Map) {
data <- map["data"]
message <- map["message"]
error <- map["error"]
}
}
class UserMapper :Mappable{
var email:String?
var fullName:String?
var id:CLong?
var phoneNumber:String?
var token:CLong?
required init?(_ map: Map) {
}
func mapping(map: Map) {
email <- map["email"]
fullName <- map["fullName"]
id <- map["id"]
phoneNumber <- map["phoneNumber"]
token <- map["token"]
phoneNumber <- map["phoneNumber"]
}
}
In my Android project I use the Gson dependency and I was able to use my JSON as an object
class ErrorMapper:Mappable{
var message:String?
var code:Int?
required init?(_ map: Map) {
}
func mapping(map: Map) {
message <- map["message"]
code <- map["code"]
}
}
This is the Alamofire that gave me the JSON.
func login(params: [String:Any]){Alamofire.request
("http://192.168.0.192:8081/SpringBoot/user/login", method: .post,
parameters: params,encoding: JSONEncoding.default, headers:
headers).responseJSON {
response in
switch response.result {
case .success:
let response = Mapper<ResponseObjectMapper<UserMapper,ErrorMapper>>.map(JSONString: response.data)
break
case .failure(let error):
print(error)
}
}
}
If I print the response with print(response) I got
SUCCESS: {
data = {
email = "vpozo#montran.com";
fullName = "Victor Pozo";
id = 6;
phoneNumber = 099963212;
token = 6;
};
error = "<null>";
message = SUCCESS;
}
and if I use this code I can got a result with key and value but I don't know how to use it as an object
if let result = response.result.value {
let responseDict = result as! [String : Any]
print(responseDict["data"])
}
console:
Optional({
email = "vpozo#gmail.com";
fullName = "Victor Pozo";
id = 6;
phoneNumber = 099963212;
token = 6;
})
I would like to use it in an Object, like user.token in a View Controller, probably I'm really confused, trying to map with generic attributes.
Type 'ResponseObjectMapper<UserMapper, ErrorMapper>' does not conform to protocol 'BaseMappable'
First of all you will need a Network Manager which uses Alamofire to make all your requests. I have made generalized one that looks something like this. You can modify it as you want.
import Foundation
import Alamofire
import SwiftyJSON
class NetworkHandler: NSObject {
let publicURLHeaders : HTTPHeaders = [
"Content-type" : "application/json"
]
let privateURLHeaders : HTTPHeaders = [
"Content-type" : "application/json",
"Authorization" : ""
]
enum RequestType {
case publicURL
case privateURL
}
func createNetworkRequestWithJSON(urlString : String , prametres : [String : Any], type : RequestType, completion:#escaping(JSON) -> Void) {
let internetIsReachable = NetworkReachabilityManager()?.isReachable ?? false
if !internetIsReachable {
AlertViewManager.sharedInstance.showAlertFromWindow(title: "", message: "No internet connectivity.")
} else {
switch type {
case .publicURL :
commonRequest(urlString: baseURL+urlString, parameters: prametres, completion: completion, headers: publicURLHeaders)
break
case .privateURL:
commonRequest(urlString: baseURL+urlString, parameters: prametres, completion: completion, headers: privateURLHeaders)
break
}
}
}
func commonRequest(urlString : String, parameters : [String : Any], completion : #escaping (JSON) -> Void , headers : HTTPHeaders){
print("urlString:"+urlString)
print("headers:")
print(headers)
print("parameters:")
print(parameters)
let url = NSURL(string: urlString)
var request = URLRequest(url: url! as URL)
request.httpMethod = "POST"
request.httpHeaders = headers
request.timeoutInterval = 10
let data = try! JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions.prettyPrinted)
let json = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
if let json = json {
print("parameters:")
print(json)
}
request.httpBody = json!.data(using: String.Encoding.utf8.rawValue)
let alamoRequest = AF.request(request as URLRequestConvertible)
alamoRequest.validate(statusCode: 200..<300)
alamoRequest.responseJSON{ response in
print(response.response?.statusCode as Any )
if let status = response.response?.statusCode {
switch(status){
case 201:
print("example success")
SwiftLoader.hide()
case 200 :
if let json = response.value {
let jsonObject = JSON(json)
completion(jsonObject)
}
default:
SwiftLoader.hide()
print("error with response status: \(status)")
}
}else{
let jsonObject = JSON()
completion(jsonObject)
SwiftLoader.hide()
}
}
}
}
After this when ever you need to make a request you can use this function. This will take in parameters if any needed and once the request is complete it will execute a call back function in which you can handle the response. The response here will be of SWIFTYJSON format.
func makeNetworkRequest(){
let networkHandler = NetworkHandler()
var parameters : [String:String] = [:]
parameters["email"] = usernameTextField.text
parameters["pass"] = passwordTextField.text
networkHandler.createNetworkRequestWithJSON(urlString: "http://192.168.0.192:8081/SpringBoot/user/login", prametres: parameters, type: .publicURL, completion: self.handleResponseForRequest)
}
func handleResponseForRequest(response: JSON){
if let message = response["message"].string{
if message == "SUCCESS"{
if let email = response["data"]["email"].string{
//Do something with email.
}
if let fullName = response["data"]["fullName"].string{
//Do something with fullName.
}
if let id = response["data"]["id"].int{
//Do something with id.
}
if let phoneNumber = response["data"]["phoneNumber"].int64{
//Do something with phoneNumber.
}
if let token = response["data"]["token"].int{
//Do something with token.
}
}else{
//Error
}
}
}
Hope this helps. Let me know if you get stuck anywhere.

Adding nested dictionary causes JSONSerialization to return nil

I have the following structure that is used to pass JSON data to a REST endpoint. Originally the object contained only one level of key-value pairs. In that scenario, serializing to a JSON object worked properly.
Now I need to add a dictionary as a parameter, which should create a nested dictionary in the resulting JSON. However, adding the nested dictionary causes JSONSerialization to return nil.
Code:
struct ServicePayload:Codable {
private var name:String
private var type:String
private var deviceId:String
private var clientType:String
private var appInstanceId:String
private var version:String
private var addParams:[String:String] // causes failure
init(name:String, type:String, version:String, params:[String:String]) {
self.name = name
self.type = type
self.deviceId = Constants.Device.identifier!
self.version = version
self.clientType = "1"
self.appInstanceId = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
self.addParams = params
}
// json mapper
private enum CodingKeys:String, CodingKey {
case name = "name"
case type = "contentTypes"
case deviceId = "x-DeviceId"
case clientType = "x-ClientType"
case appInstanceId = "x-InstanceId"
case version = "version"
case addParams = "optionalParams"
}
func getJsonObject() -> [String:String]? {
do {
let encoded = try JSONEncoder().encode(self)
if let json = try JSONSerialization.jsonObject(with: encoded, options: []) as? [String : String] {
return json
}
} catch (let error) {
print("Error building JSON: \(error.localizedDescription)")
}
return nil
}
}
Without the the addParams field, the JSONSerialization works as expected. When I add the addParams field, which adds a nested dictionary to the object, the JSONSerialization fails and returns nil.
Can anyone give me a clue as to why I can't add a nested dictionary in this scenario?
Thanks!
It fails as one key (here it's the added addParams ) 's value isn't a String so the cast
as? [String : String] // causes failure
Won't occur , hence a nil json , so Replace
if let json = try JSONSerialization.jsonObject(with: encoded, options: [])
as? [String : String] {
with
if let json = try JSONSerialization.jsonObject(with: encoded, options: [])
as? [String : Any] {
Any encapsulates String and [String:String]

how to deserialize a collection from an alamofire request

this is a pretty basic question of the syntax for deserializing responsejson collection in alamofire and swift
I have a GET service that returns an array of user objects e.g.
[
{"uname": "bob"},
{"uname": "jane"}
]
and here is my request
Alamofire.request(.GET, url , encoding:.JSON)
.responseJSON(options: .MutableContainers, completionHandler:{ (request, response, JSON, error) -> Void in
let result = JSON as? [Dictionary<String,String>]
if (response!.statusCode == 200 && error == nil){
//how should I deserialize the result here to return [String]
callback(success: true, errorMsg: nil, friends:friends)
}
else{
callback(success: false,errorMsg:error?.localizedDescription,friends:nil)
}
})
My question is how to deserialize the result, and am I correct to assume that the json result is a [Dictionary] and in order for me to change it to a [String] result, should I map it?
If I use the map syntax below I have a few questions
let friends = result?.map {
return $0["uname"] as String?
}
With the optional return value can I get it to return a [String] instead of a [String?] in a graceful way - isn't there a way to let map return only non null values somehow in a concise syntax?
Also is there a better syntax for map that lets me name the parameters instead of using $0?
I use Alamofire with SwiftyJSON to do the job and deserialise array of JSON objects to native objects. Here is an example of how I do it:
Alamofire.request(request)
.responseJSON(options: nil) { (request, response, json, error) -> Void in
if let requestError = error as NSError?{
callback!(nil, nil, requestError)
}
if let httpResponse = response as NSHTTPURLResponse?{
if(httpResponse.statusCode != 200){
callback!(nil, httpResponse, nil)
} else{
let jsonResult = JSON(json!) as JSON
var concerts = self.deserializeConcerts(jsonResult)
callback!(concerts, nil, nil)
}
}
}
Deserialize concerts looks like this:
class private func deserializeConcerts(concerts: SwiftyJSON.JSON) -> [ConcertModel]{
let concertsArray = concerts.object as! NSMutableArray
var concertObjs = [ConcertModel]()
for concert in concertsArray{
let concertObj = ConcertModel(concert: concert as! NSDictionary)
concertObjs.append(concertObj)
}
return concertObjs
}
And finally, ConcertModel initialiser just maps the values of the NSDictionary to the properties of the ConcertModel object.
Updated implementation for Alamofire 4.3
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.data != nil {
let json = JSON(data: response.data!)
let name = json["data"][0]["name"].string
if name != nil {
print(name!)
}
}
}
That will parse this JSON input:
{
data: [
{ name: 'John' },
{ name: 'Dave' }
]
}