Swift: Unable to parse JSON response to model - json

I am getting data response from server like below:
{
"success": true,
"data": {
"cash": 0,
"newCash": 0,
"cashExpireAt": "2019-02-26T16:01:35.6451887+08:00",
"isEnabled": false,
"low_balance": true
}
}
//Below is Model Class
class WalletBalance: Mappable {
var isSuccess : Bool! = false
var data : WalletData?
required init?(map: Map) {
//Code here
}
func mapping(map: Map) {
isSuccess <- map["success"]
data <- map["data"]
}
}
class WalletData: Mappable {
var cash : Int! = 0
var newCash : Int! = 0
var cashExpireAt : String! = ""
var isEnabled : Bool! = false
var low_balance : Bool! = false
required init?(map: Map) {
//Code here
}
func mapping(map: Map) {
cash <- map["cash"]
newCash <- map["newCash"]
cashExpireAt <- map["cashExpireAt"]
isEnabled <- map["isEnabled"]
low_balance <- map["low_balance"]
}
}
//Below is code to call server api
Alamofire.request(url!, method: .get, parameters: nil, encoding: URLEncoding.default, headers: walletHeaders)
.validate(statusCode: 200..<600)
.responseObject { (response: DataResponse<WalletBalance>) in
switch response.result {
case .success:
if response.result.value == nil {
//Code here
} else {
completion(response.result.value!)
}
break
case .failure(let error):
print("error: ", error.localizedDescription)
break
}
}
I am getting error:
error: The operation couldn’t be completed. ObjectMapper failed to
serialize response.
I tried to call api using postman but its working fine in Postman.

Have you tried using Decodable instead of Mappable as suggested by the Alamofire documentation?

Use Decodable. It's much easier, more efficient, built-in and it generates much less code. The date can be decoded directly as Date
let jsonString = """
{
"success": true,
"data": {
"cash": 0,
"newCash": 0,
"cashExpireAt": "2019-02-26T16:01:35.6451887+08:00",
"isEnabled": false,
"low_balance": true
}
}
"""
struct Balance : Decodable {
let success : Bool
let data : Wallet
}
struct Wallet: Decodable {
let cash, newCash : Int
let cashExpireAt : Date
let isEnabled, lowBalance : Bool
}
let data = Data(jsonString.utf8)
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let result = try decoder.decode(Balance.self, from: data)
print(result)
} catch { print(error) }

Related

Unable to decode JSON data while parsing in swift

I am using generic codable protocol for JSON parsing
below is the JSON structure and postman response
{
"jsonrpc": "2.0",
"result": {
"categories": [
{
"id": 91,
"title": "ADMINISTRATION & SECRETARIAL",
},
{
"id": 62,
"title": "BEAUTY",
},
{
"id": 325,
"title": "CARE",
for above response i have created model like below:
struct CategoryModel: Codable {
let jsonrpc: String?
let result: ResultCat?
}
struct ResultCat: Codable {
let categories: [Categorymine]?
}
struct Categorymine: Codable {
let id: Int?
let title, slug: String?
}
code: in this code i am getting response above switch response.result case and data also getting (11113 bytes).. but here i am unable to decode data i think.. when i call service not getting response
please guide me to get response
first time i am working with generics, where am i wrong. please guide me
import Foundation
import Alamofire
class GeneralResponse<T: Codable>: Codable {
var jsonrpc: String
var result: T?
}
struct RequestObject {
var method: HTTPMethod
var path: String
var token: String?
var params: [String: Any]?
}
class WebService {
private let decoder: JSONDecoder
public init(_ decoder: JSONDecoder = JSONDecoder()) {
self.decoder = decoder
}
public func serviceCall<T: Codable>(_ objectType: T.Type,
with request: RequestObject,
completion: #escaping (GeneralResponse<T>?, Error?) -> Void) {
var headerParams: HTTPHeaders?
AF.request(request.path, method: request.method, parameters: request.params,encoding: JSONEncoding.default, headers: headerParams)
.responseJSON { response in
print("Json response: \(response)")//getting response here as well
switch response.result {
case .success(let dictData):
let JSON = dictData as? [String : Any]
do {
let data = response.data
print("only data \(data)")//Optional(11113 bytes)
let responseData = try self.decoder.decode(GeneralResponse<T>.self, from: data ?? Data())
print("model data \(String(describing: responseData.result))")//Optional(TestigAllInOne.CategoryModel(jsonrpc: nil, result: nil))
} catch {
completion(nil, error)
}
case .failure(let error):
let error = error
print(error.localizedDescription)
}
}
}
}
in console i am getting like this:
only data Optional(11113 bytes)
model data Optional(TestigAllInOne.CategoryModel(jsonrpc: nil, result: nil))
i am using service call in viewcontroller like this:
var catData: CategoryModel? {
didSet {}
}
func genericCall(){
let request = RequestObject(method: .get, path: "https://tt.com/dev/api/get-category", params: nil)
WebService().serviceCall(CategoryModel.self, with: request) { (response, error) in
if let items = response?.result {
print("viewcontroller response \(items)")
DispatchQueue.main.sync {
self.catData = items
self.tableView.reloadData()
}
}
else{
print(error)
}
}
}
Acording to your Model T should be of type ResultCat and not CategoryModel
WebService().serviceCall(ResultCat.self, with: request)
look at your generic class:
class GeneralResponse<T: Codable>: Codable {
var jsonrpc: String
var result: T?
}
so T in this context is the type of the result var.
Edit to address the comment:
if i want to pass whole CategoryModel in serviceCall then how to change my code.
I asume you want to call the function as described in the question. To achieve this simply get rid of your generic struct as you wouldn´t use it in that case.
public func serviceCall<T: Codable>(_ objectType: T.Type,
with request: RequestObject,
completion: #escaping (T?, Error?) -> Void) {
and:
let responseData = try self.decoder.decode(T.self, from: data ?? Data())

How to read and display a dictionary from JSON?

I am working on an app that fetches the data from JSON and displays it.
However, I am stuck with an error saying Instance method 'appendInterpolation(_:formatter:)' requires that '[String : Int]' inherit from 'NSObject'
Here is my data structure:
struct Data: Codable {
var message: String
var data: Objects
}
struct Objects: Codable {
var date: String
var day: Int
var resource: String
var stats, increase: [String: Int]
}
Function to fetch the data:
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(Data.self, from: data)
self.data = decodedData
} catch {
print("Hey there's an error: \(error.localizedDescription)")
}
}
}.resume()
}
And a ContentView with the #State property to pass the placeholder data:
struct ContentView: View {
#State var data = Data(message: "", data: Objects(date: "123", day: 123, resource: "", stats: ["123" : 1], increase: ["123" : 1]))
var body: some View {
VStack {
Button("refresh") { getData() }
Text("\(data.data.date)")
Text("\(data.data.day)")
Text(data.message)
Text("\(data.data.stats)") //error is here
Here is an example of JSON response
I wonder if the problem is in data structure, because both
Text("\(data.data.date)")
Text("\(data.data.day)")
are working just fine. If there are any workarounds with this issue – please, I would highly appreciate your help!:)
stats is [String: Int], and so when you want to use it, you need to supply the key to get the value Int, the result is an optional that you must unwrap or supply a default value in Text
So use this:
Text("\(data.data.stats["123"] ?? 0)")
And as mentioned in the comments, do not use Data for your struct name.
EDIT-1: there are two ways you can make the struct fields camelCase; one is using the CodingKeys as shown in ItemModel, or at the decoding stage, as shown in the getData() function. Note, I've also updated your models to make them easier to use.
struct DataModel: Codable {
var message: String
var data: ObjectModel
}
struct ObjectModel: Codable {
var date: String
var day: Int
var resource: String
var stats: ItemModel
var increase: ItemModel
}
struct ItemModel: Codable {
var personnelUnits: Int
var tanks: Int
var armouredFightingVehicles: Int
// ...
// manual CodingKeys
// enum CodingKeys: String, CodingKey {
// case tanks
// case personnelUnits = "personnel_units"
// case armouredFightingVehicles = "armoured_fighting_vehicles"
// }
}
struct ContentView: View {
#State var dataModel = DataModel(message: "", data: ObjectModel(date: "123", day: 123, resource: "", stats: ItemModel(personnelUnits: 123, tanks: 456, armouredFightingVehicles: 789), increase: ItemModel(personnelUnits: 3, tanks: 4, armouredFightingVehicles: 5)))
var body: some View {
VStack {
Button("get data from Server") { getData() }
Text("\(dataModel.data.date)")
Text("\(dataModel.data.day)")
Text(dataModel.message)
Text("\(dataModel.data.stats.armouredFightingVehicles)") // <-- here
}
}
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // <-- here
dataModel = try decoder.decode(DataModel.self, from: data)
} catch {
print("--> error: \(error)")
}
}
}.resume()
}
}
}

SwiftUI NavigationLink cannot find 'json' in scope

I'm new to SwiftUI and have worked through the server requests and JSON. I now need to programmatically transition to a new view which is where I get stuck with a "Cannot find 'json' in scope" error on the NavigationLink in ContentView.swift. I've watched videos and read articles but nothing quite matches, and everything I try just seems to make things worse.
JSON response from server
{"status":{"errno":0,"errstr":""},
"data":[
{"home_id":1,"name":"Dave's House","timezone":"Australia\/Brisbane"},
{"home_id":2,"name":"Mick's House","timezone":"Australia\/Perth"},
{"home_id":3,"name":"Jim's House","timezone":"Australia\/Melbourne"}
]}
JSON Struct file
import Foundation
struct JSONStructure: Codable {
struct Status: Codable {
let errno: Int
let errstr: String
}
struct Home: Codable, Identifiable {
var id = UUID()
let home_id: Int
let name: String
let timezone: String
}
let status: Status
let data: [Home]
}
ContentView file
import SwiftUI
struct ContentView: View {
#State private var PushViewAfterAction = false
var body: some View {
NavigationLink(destination: ListView(json: json.data), isActive: $PushViewAfterAction) {
EmptyView()
}.hidden()
Button(action: {
Task {
await performAnAction()
}
}, label: {
Text("TEST")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue.cornerRadius(10))
.foregroundColor(.white)
.font(.headline)
})
}
func performAnAction() {
PushViewAfterAction = true
return
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ListView file
import SwiftUI
struct ListView: View {
#State var json: JSONStructure
var body: some View {
VStack {
List (self.json.data) { (home) in
HStack {
Text(home.name).bold()
Text(home.timezone)
}
}
}.onAppear(perform: {
guard let url: URL = URL(string: "https://... ***removed*** ") else {
print("invalid URL")
return
}
var urlRequest: URLRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// check if response is okay
guard let data = data, error == nil else { // check for fundamental networking error
print((error?.localizedDescription)!)
return
}
let httpResponse = (response as? HTTPURLResponse)!
if httpResponse.statusCode != 200 { // check for http errors
print("httpResponse Error: \(httpResponse.statusCode)")
return
}
// convert JSON response
do {
self.json = try JSONDecoder().decode(JSONStructure.self, from: data)
} catch {
print(error.localizedDescription)
print(String(data: data, encoding: String.Encoding.utf8)!)
}
print(json)
if (json.status.errno != 0) {
print(json.status.errstr)
}
print("1. \(json.data[0].name)), \(json.data[0].timezone)")
print("2. \(json.data[1].name)), \(json.data[1].timezone)")
}).resume()
})
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
I've tried to keep the code to a minimum for clarity.
It's because there is no "json" in ContentView, you need to pass json object to ListView, but since you load json in ListView, then you need to initialize json in ListView like:
struct ListView: View {
#State var json: JSONStructure = JSONStructure(status: JSONStructure.Status(errno: 0, errstr: ""), data: [JSONStructure.Home(home_id: 0, name: "", timezone: "")])
var body: some View {
and remove it form NavigationLink in ContentView like:
NavigationLink(destination: ListView(), isActive: $PushViewAfterAction) {
or you could build your JSONStructure to accept optional like:
import Foundation
struct JSONStructure: Codable {
struct Status: Codable {
let errno: Int?
let errstr: String?
init() {
errno = nil
errstr = nil
}
}
struct Home: Codable, Identifiable {
var id = UUID()
let home_id: Int?
let name: String?
let timezone: String?
init() {
home_id = nil
name = nil
timezone = nil
}
}
let status: Status?
let data: [Home]
init() {
status = nil
data = []
}
}
but then you need to check for optionals or provide default value like:
struct ListView: View {
#State var json: JSONStructure = JSONStructure()
var body: some View {
VStack {
List (self.json.data) { (home) in
HStack {
Text(home.name ?? "Could not get name").bold()
Text(home.timezone ?? "Could not get timeZone")
}
}
}.onAppear(perform: {
guard let url: URL = URL(string: "https://... ***removed*** ") else {
print("invalid URL")
return
}
var urlRequest: URLRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// check if response is okay
guard let data = data, error == nil else { // check for fundamental networking error
print((error?.localizedDescription)!)
return
}
let httpResponse = (response as? HTTPURLResponse)!
if httpResponse.statusCode != 200 { // check for http errors
print("httpResponse Error: \(httpResponse.statusCode)")
return
}
// convert JSON response
do {
self.json = try JSONDecoder().decode(JSONStructure.self, from: data)
} catch {
print(error.localizedDescription)
print(String(data: data, encoding: String.Encoding.utf8)!)
}
print(json)
if (json.status?.errno != 0) {
print(json.status?.errstr)
}
print("1. \(json.data[0].name)), \(json.data[0].timezone)")
print("2. \(json.data[1].name)), \(json.data[1].timezone)")
}).resume()
})
}
}

mapping string response with ObjectMapper lib in swift

I try to map a string response with object mapper by my result base.
this is result class :
import ObjectMapper
class Result< T : Mappable > : Mappable {
var data: T?
var status: String?
var message: String?
var error: String?
init?(data: T?, status: String?, error: String?){
self.data = data
self.status = status
self.error = error
}
required init?(map: Map){
}
func mapping(map: Map) {
data <- map["data"]
status <- map["status"]
message <- map["message"]
error <- map["error"]
}
}
and also this is my network class:
import Foundation
import ObjectMapper
final class Network<T:Mappable>{
init() {
}
open func requestItem(_ router: BaseRouter, completionHandler: #escaping (Any?, Error?) -> Void) {
APIClient.Instance.requestJSON(router) { (response, error) in
if let error = error {
completionHandler(nil, APIError(code:ErrorCode.NetworkFailed, message:error.localizedDescription))
}
else if let json = response {
var convertedString : String?
do {
let data1 = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted)
convertedString = String(data: data1, encoding: String.Encoding.utf8)
print(convertedString!.description)
} catch let myJSONError {
print(myJSONError)
}
let result : Result<T>? = Mapper<Result<T>>().map(JSONString: convertedString!)
if let success = result?.status, success == "success" {
completionHandler(result?.data, nil)
}
else {
completionHandler(nil, APIError(code:ErrorCode.HandledInternalError, message:(result?.error)!))
}
}
else {
completionHandler(nil, APIError(code:ErrorCode.EmptyJSONException, message:"Empty JSON Exception"))
}
}
}
}
the response is :
{
"status" : "success",
"data" : "01CPSE6AQXVK554MTGENETKW24"
}
I try to map it but because of String is not a mappable class, I can not do. map["data"] variable should assign only string, not another complex class. Is there anyone can help me about this problem?
Finally error is :
Finally I can relies that what the wrong is with this code.
extension String : Mappable {
}
That's all.

Parsing JSON data from alamofire into Array with Dictionary

I'm trying to parse JSON data from alamorefire as follows.
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "https://api.mynexttrainschedule.net/")
.responseJSON { response in
guard let object = response.result.value else {
print("Oh, no!!!")
return
}
let json = JSON(object);print(json)
let schedule = json[0]["schedule"]
}
}
}
If I print json, I have a data structure like the following (stated concisely).
[
{
"schedule" : [
{"departureTime" : "05:09", "destination" : "Boston", "trainType" : "Express"},
{"departureTime" : "05:19", "destination" : "Portland", "trainType" : "Rapid"},
{"departureTime" : "05:29", "destination" : "Boston", "trainType" : "Express""}
],
"station" : "Grand Central",
"direction" : "North"
},
{
"schedule" : [
{"departureTime" : "05:11","destination" : "Washington, "trainType" : "Express""},
{"departureTime" : "05:23","destination" : "Baltimore, "trainType" : "Express""},
{"departureTime" : "05:35","destination" : "Richmond, "trainType" : "Local""}
],
"station" : "Grand Central",
"direction" : "South"
}
]
Now, how can I save the schedule array with a dictionary (departureTime, destination...) through or not through SwiftyJSON?
Thanks.
UPDATE
The following is my own solution.
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
var scheduleArray = [Dictionary<String,String>]()
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "https://api.mynexttrainschedule.net/")
.responseJSON { response in
guard let object = response.result.value else {
print("Oh, no!!!")
return
}
let json = JSON(object)
if let jArray = json.array {
if let westHolidayArray = jArray[0]["schedule"].array {
for train in westHolidayArray {
if let time = train["departureTime"].string,
let dest = train["destination"].string,
let type = train["trainType"].string {
let dict = ["time":time, "dest":dest, "type": type]
self.scheduleArray.append(d)
}
}
}
}
}
}
}
First of all you should create a class that is your model of Schedule like this
class Schedule: NSObject {
var departureTime: String
var destination: String
var trainType: String
init(jsonDic : NSDictionary) {
self.departureTime = jsonDic["departureTime"] != nil ? jsonDic["departureTime"] as! String! : nil
self.destination = jsonDic["destination"] != nil ? jsonDic["destination"] as! String! : nil
self.trainType = jsonDic["trainType"] != nil ? jsonDic["trainType"] as! String : nil
}
}
And in your view controller your going to need an array of the Schedule object and after you could parse your Json you do it like this:
class ScheduleController: UIViewController {
// The two object use to show the spinner loading
var loadingView: UIView = UIView()
var spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
// Array of your objects
var arrSchedule: [Schedule] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.getInfoSchedule()
}
func getInfoSchedule() {
showActivityIndicator()
Alamofire.request("https://api.mynexttrainschedule.net/", method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON {
response in
self.hideActivityIndicator()
switch response.result {
case .success:
if let objJson = response.result.value as! NSArray? {
for element in objJson {
let data = element as! NSDictionary
if let arraySchedule = data["schedule"] as! NSArray? {
for objSchedule in arraySchedule {
self.arrSchedule.append(Schedule(jsonDic: objSchedule as! NSDictionary))
}
}
}
}
case .failure(let error):
print("Error: \(error)")
}
}
}
//Those two method serves to show a spinner when the request is in execution
func showActivityIndicator() {
DispatchQueue.main.async {
self.loadingView = UIView()
self.loadingView.frame = CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height)
self.loadingView.center = self.view.center
self.loadingView.backgroundColor = UIColor(rgba: "#111111")
self.loadingView.alpha = 0.9
self.loadingView.clipsToBounds = true
self.spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
self.spinner.frame = CGRect(x: 0.0, y: 0.0, width: 80.0, height: 80.0)
self.spinner.center = CGPoint(x:self.loadingView.bounds.size.width / 2, y:self.loadingView.bounds.size.height / 2)
self.loadingView.addSubview(self.spinner)
self.view.addSubview(self.loadingView)
self.spinner.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.spinner.stopAnimating()
self.loadingView.removeFromSuperview()
}
}
}
Maybe is not the more efficient way to do it, but it worked for me. I'm using swift3 with xcode 8.1.
Hope it helps !
Basically what you have is an array of schedules. You can map it using ObjectMapper. Install its pod and just create a new Swift file. and Write this
import ObjectMapper
class TrainSchedules: Mappable {
var mySchedules: [Schedules]
required init?(_ map: Map) {
mySchedules = []
}
func mapping(map: Map) {
mySchedules <- map["schedule"]
}
}
class Schedules: Mappable {
var departureTime: String
var destination: String
var trainType: String
required init?(_ map: Map) {
departureTime = ""
destination = ""
trainType = ""
}
func mapping(map: Map) {
departureTime <- map["departureTime"]
destination <- map["destination"]
trainType <- map["trainType"]
}
}
Now you can use it like
if let data = Mapper<TrainSchedules>().map(json){
// now data is an array containt=g all the schedules
// access departureTimelike below
print(data[0].departureTime)
}
I hope it helps, Letme know if you find any difficulty.
Alamofire.request("YOUR_URL", method:.post, parameters:params, encoding:URLEncoding.default, headers: nil).responseJSON { response in
switch(response.result)
{
case .success(_):
if response.result.value != nil
{
let dict :NSDictionary = response.result.value! as! NSDictionary
print(dict)
let status = dict.value(forKey: "status")as! String
print(status)
if(status=="1")
{
self.array_placeRequestId=((dict.value(forKeyPath: "result.request_id") as! NSArray).mutableCopy() as! NSMutableArray)
}
else
{
print("Something Missed")
}
}
break
case .failure(_):
print(response.result.error)
break
}
}