This question already has answers here:
saving swifty json array to user defaults
(4 answers)
Closed 2 years ago.
I made a post request using Alamofire. This is my code to fetch data from server using an endpoint:
Alamofire.request("http://192.168.80.21:3204/api/auth/signin", method: .post, parameters: parameters,encoding: JSONEncoding.default, headers: nil).responseJSON {
response in
switch response.result {
case .success(let value):
let json = JSON(value)
print(json)
break
case .failure(let error):
print("Error :- \(error)")
}
}
}
And this is the data i get from the server:
{
"accessToken" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTIsInJvbGUiOjEsImlhdCI6MTYwMzg2ODUxNCwiZXhwIjoxNjAzOTU0OTE0fQ.y68w8XQfqFZDVgaxiuFuVCOqaI5e5vZ-SfoDB_Ctxro",
"role" : "admin",
"auth" : true
}
I want to save the response to UserDefault for further use. Help me to save the data to Userdefault and retrieve and print data.
Create a model, a struct conforming to Codable
struct Auth: Codable {
let accessToken, role: String
let auth: Bool
}
and extend UserDefaults
extension UserDefaults {
func auth(forKey defaultName: String) -> Auth? {
guard let data = data(forKey: defaultName) else { return nil }
do {
return try JSONDecoder().decode(Auth.self, from: data)
} catch { print(error); return nil }
}
func set(_ value: Auth, forKey defaultName: String) {
let data = try? JSONEncoder().encode(value)
set(data, forKey: defaultName)
}
}
Now you can use auth(forKey:) and set(_:forKey:) to read and write an Auth instance directly.
Drop SwiftyJSON and change the Alamofire part to decode the JSON into the struct with JSONDecoder
Alamofire.request("http://192.168.80.21:3204/api/auth/signin", method: .post, parameters: parameters).responseData {
response in
switch response.result {
case .success(let data):
do {
let auth = try JSONDecoder().decode(Auth.self, from: data)
print(auth)
UserDefaults.standard.set(auth, forKey: "Auth")
} catch { print(error) }
case .failure(let error):
print("Error :- \(error)")
}
}
To read the instance write
let auth = UserDefaults.standard.auth(forKey: "Auth")
accessToken
func setInDefault<T: Codable>(key: String, type: T.Type, data: T?) {
guard let data = data else {
return
}
do {
let encoder = JSONEncoder()
let encodeData = try encoder.encode(data)
let dict:[String:Any] = try JSONSerialization.jsonObject(with: encodeData, options: .allowFragments) as! [String : Any]
let data = NSKeyedArchiver.archivedData(withRootObject: dict)
//set user data in defaults
UserDefaults.standard.set(data, forKey: key)
UserDefaults.standard.synchronize()
} catch {
print(error)
//handle error
}
}
func getFromDefault<T: Codable>(key: String, type: T.Type) -> T? {
let archieved = UserDefaults.standard.value(forKey: key)
let dict = NSKeyedUnarchiver.unarchiveObject(with: archieved as? Data ?? Data())
let decoder = JSONDecoder()
do {
if let dic = dict {
let data = try JSONSerialization.data(withJSONObject: dic, options: .prettyPrinted)
return try decoder.decode(type, from: data)
}
} catch {
print(error)
//handle error
}
return nil
}
You can use the above func. like below.
setInDefault(key: "userData", type: AuthUser.self, data: data)
Here,
Param: data is a generic parameter of type T. So here you need to pass the response data you get from server. (It should be Codable class type)
Param: AuthUser is
struct AuthUser : Codable {
let accessToken : String?
let role: String?
let auth: Bool?
enum CodingKeys: String, CodingKey {
case accessToken = "accessToken"
case role = "role"
case auth = "auth"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
accessToken = try values.decodeIfPresent(String.self, forKey: .accessToken)
role = try values.decodeIfPresent(String.self, forKey: .role)
auth = try values.decodeIfPresent(Bool.self, forKey: .auth)
}
}
To get data:
let data = getFromDefault(key: "userData", type: AuthUser.self)
print(data)
If you use global variable. You can use save method according to your purpose which is written below :
let objects = [yourObjectClass]()
func save() {
let jsonEncoder = JSONEncoder()
if let savedData = try? jsonEncoder.encode(objects) {
let defaults = UserDefaults.standard
defaults.set(savedData, forKey: "objects")
} else {
print("Failed to save objects.")
}
}
}
You can save JSON Model data in Userdefaults Using below function code
func saveData(data: yourModelData){
var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: yourModelData)
userDefaults.set(encodedData, forKey: "yourKey")
userDefaults.synchronize()
}
and retrive Data from Userdefaults use this function
func getData() -> yourModelData {
var userDefaults = UserDefaults.standard
let decoded = userDefaults.data(forKey: "yourKey")
let decodedData = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [yourModelData]
return decodedData
}
Related
I can't getting json value into variable.I'm printing value but the problem is I can't getting json value without array
here is my json
{
"Categories": [
"city",
"delhi"
]
}
I want to categories value with array im printing value with array
here is my code
do{
let json = try JSONSerialization.jsonObject(with: data!, options: []) as! [String: AnyObject]
print(json as AnyObject)
if let Categories = json["Categories"] {
print(Categories)
}
You need
do {
let json = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:[String]]
let arr1 = json["Categories"]!
let str1 = arr1.joined(separator: ":")
print(str1)
// or
let decoded = try JSONDecoder().decode(Root.self, from: data)
let str = decoded.categories.joined(separator: ":")
print(str)
} catch {
print(error)
}
or use
struct Root: Codable {
let categories: [String]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
}
}
Make your life easier with Codable. First create custom model for your response
struct Response: Decodable {
let categories: [String]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
}
}
Then decode your data which your receive using JSONDecoder
if let data = data {
do {
let decoded = try JSONDecoder().decode(Response.self, from: data)
let string = decoded.categories.joined(separator: ", ") // if u need to join
// your array to
// single `String`
print(string)
} catch { print(error) }
}
Use built in swift support for decoding json by conforming to Decodable and also conform to CustomStringConvertible to get a string representation of the values
struct Item: Decodable, CustomStringConvertible {
let categories: [String]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
}
public var description: String {
return categories.joined(separator: " ")
}
}
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
let descr = result.description
print(descr)
} catch {
print(error)
}
//Model Class should be like this
struct JsonResposne : Codable {
let categories : [String]?
enum CodingKeys: String, CodingKey {
case categories = "Categories"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
categories = try values.decodeIfPresent([String].self, forKey: .categories)
}
}
func getCategoriesResponse() {
Alamofire.request(requestUrl, method: .post, parameters: params, encoding: URLEncoding.default).responseJSON { (response) in
switch response.result {
case .success:
if response.data != nil {
do {
let decoder = JSONDecoder()
let apiResponse:JsonResponse = try decoder.decode(JsonResponse.self, from: responseData)
print(apiResponse.categories.count)
}catch {
print(error.localizedDescription)
}
}
}
break
case .failure:
print("There was something with your call")
break
}
}
}
Needs to get country name from below api call :
https://restcountries.eu/rest/v1/all
My code :
var arrRes = []
func getCountry() {
let Url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(Url).responseJSON { (responseData) -> Void in
do {
if let datas = responseData.result.value {
let data = (datas as AnyObject).data(using: .utf8)!
let parseData = try JSONSerialization.jsonObject(with: data, options: [])
for country in parseData {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
}
}
getting error here : 'Any' is not convertible to 'AnyObject' on below line let data = (datas as AnyObject).data(using: .utf8)!..
I need to get only name and append to my array.Any other idea or solution to achieve that ?
Replace do catch block of statement with this.
do {
if let countries = responseData.result.value as? [[String: Any]] {
for country in countries {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
Try this, its working fine for me.
let urlStr = "https://restcountries.eu/rest/v1/all"
let setFinalURl = urlStr.addingPercentEncoding (withAllowedCharacters: .urlQueryAllowed)!
var request = URLRequest(url: URL(string: setFinalURl)!)
request.httpMethod = HTTPMethod.get.rawValue
Alamofire.request(request).responseJSON
{ (responseObject) -> Void in
if responseObject.result.isSuccess
{
print(responseObject.result.value!)
if "\(String(describing: responseObject.response!.statusCode))" == "200"
{
let result = responseObject.result.value! as AnyObject
let countryNamesArr = result.value(forKey: "name") as! NSArray
print(countryNamesArr)
}
else
{
// handle error
}
}
if responseObject.result.isFailure
{
let error : Error = responseObject.result.error!
print(error.localizedDescription)
}
}
You can try
struct Root: Codable {
let name: String
}
func getCountry() {
let urlStr = "https://restcountries.eu/rest/v1/all"
Alamofire.request(urlStr).responseData { (data) in
do {
guard let data = data.data else { return }
let res = try JSONDecoder().decode([Root].self,from:data)
print(res)
}
catch {
print(error)
}
}
}
Just remove this line
let data = (datas as AnyObject).data(using: .utf8)!
and in optional binding just assign data, since value is of type Data?, from optional binding you get Data
if let data = responseData.result.value
then don't forget to downcast your json to array [String:Any]
...jsonObject(with: data, options: []) as? [[String:Any]]
... then don't forget to unwrap this array or you wouldn't be able to iterate through it in for each loop
Also note that since there is Codable, you should use it instead of JSONSerialization. Then you can decode your json using JSONDecoder to your own model which conforms to protocol Decodable.
As a simple approach, you could implement getCountry() like this:
func getCountry() {
let url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(url).responseJSON { response in
if let resultValue = response.result.value, let countryObjects = resultValue as? [[String: Any]] {
let countryNames = countryObjects.compactMap { $0["name"] as? String }
print(countryNames)
}
}
}
At this point, there is no need to use JSONSerialization to get the country names; According to the API response, responseData.result.value is an array of countries (dictionaries), each dictionary has a "name" value, what you should do is to map the response to an array of string. countryNames should contains what are you looking for.
The benefit of using compactMap is to avoid any nil name, so countryNames should be [String] instead of [String?].
However, if you believe that you would need to transform the whole response objects into a custom objects (instead of dictionaries), I would highly recommend to follow the approach of using Decodable.
My code, its working well for me.
Swift 5
public func getCountry(completion: #escaping ([String]) -> ()) {
let url: String = "https://restcountries.eu/rest/v1/all"
AF.request(url).responseJSON { (responseData) -> Void in
do {
guard let data = responseData.data else { return }
let res = try JSONDecoder().decode([CountryName].self,from:data)
completion(self.getCountryName(countryName: res))
}
catch {
print(error)
}
}
}
struct CountryName: Codable {
let name: String
}
private func getCountryName(countryName:[CountryName]) -> [String]{
var country:[String] = []
for index in 0...countryName.count - 1{
country.append(countryName[index].name)
}
return country
}
I got a response from web service and try to work on it to put it in a model, but i can't work with it .
this is the response.string
"{\"USER_NO\":1,\"USER_NAME\":\"المسار البديل لتكنولوجيا المعلومات\",\"TASK_CATEGORY_NO\":1,\"USER_ID_NUMBER\":\"1031523473\",\"Mobile_No\":\"0567123432\"}"
I used extension :
extension String{
func tooDictionary() -> NSDictionary {
let blankDict : NSDictionary = [:]
if let data = self.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary
} catch {
print(error.localizedDescription)
}
}
return blankDict
}
}
to convert it to dictionary then put it in object but i failed to do that
i'm sorry but i'm new developer in swift4 ,any help please
and thank
Use Decodable:
struct User: Decodable {
private enum CodingKeys : String, CodingKey {
case userNumber = "USER_NO", taskCategoryNumber = "TASK_CATEGORY_NO"
case userName = "USER_NAME", userID = "USER_ID_NUMBER", mobileNumber = "Mobile_No"
}
let userNumber, taskCategoryNumber: Int
let userName, userID, mobileNumber : String
}
let jsonString = """
{"USER_NO":1,"USER_NAME":"المسار البديل لتكنولوجيا المعلومات","TASK_CATEGORY_NO":1,"USER_ID_NUMBER":"1031523473","Mobile_No":"0567123432"}
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(User.self, from: data)
print(result)
} catch { print(error) }
Note: Don't use NSArray / NSDictionary in Swift
i'm trying to import JSON data from the v2 of coinmarketcap API. I had it working with v1 as it was an array, however the new version is a dictionary and i cant quite get my struct correct.
The API im using is : https://api.coinmarketcap.com/v2/ticker/?convert=AUD
My struct is set up as below:
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
var id: String
var symbol : String
var name : String
var priceAUD : quoteStruct
}
struct quoteStruct{
let aud : priceStruct
}
struct priceStruct{
let price : String
}
My code for fetching the data is:
var coins = [Coin]()
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
My code for fetching the data i have used the same as previously which worked with v1 of the API, however i don't think i made my struct correctly.
Thanks in advance!
Your response Changed i try to configure it by converting it to array of dictionary you will need to change quotes to be [String:priceStruct]
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id,rank,symbol, name, priceAUD = "quotes"
}
var id: Int
var rank: Int
var symbol : String
var name : String
var priceAUD : [String: priceStruct]
}
struct priceStruct : Decodable{
let price : Double
}
func getCoinData() {
var coins = [Coin]()
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], let resultData = json["data"] as? [String:Any] {
let dataObject = try JSONSerialization.data(withJSONObject: resultData.values.map({$0}) , options: .prettyPrinted)
coins = try JSONDecoder().decode([Coin].self, from: dataObject)
print(coins.count)
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
You response in data parameter should be an array rather than a dictionary. You will not be able to iterate a dictionary over undefined keys. It would be good to get your response of API updated first.
But, If you wish to continue with the existing API response, first you need to convert your response in an array and use your Decodable structs as:
struct Coin: Decodable {
var id: String
var symbol : String
var name : String
var priceAUD : QuoteStruct
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
}
struct QuoteStruct: Decodable {
let aud : PriceStruct
}
struct PriceStruct: Decodable {
let price : String
}
Update your data parsing in API block as:
guard let responseData = data else { return }
do {
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
if let jsonData = json as? [String: Any], let dataObject = jsonData["data"] as? [Int: Any] {
let coinArray = dataObject.map { $0.1 }
if let jsonData = try? JSONSerialization.data(withJSONObject: coinArray, options: .prettyPrinted) {
coins = try JSONDecoder().decode([Coin].self, from: jsonData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
} catch {
print("Error is : \n\(error)")
}
I have response string from the backend like this:
{
"status": "success",
"data": "{\"name\":\"asd\",\"address\":\"Street 1st\"}"
}
I think the problem was because the double quote (") in the data String. When I remove the double quote, the serialization was success. But the response is from backend and I have to deal with it.
Anyone can solve this problem?
Thank you.
Here is the playground code.
import Foundation
var jsonStr = """
{
"status": "success",
"data": "{\"name\":\"asd\",\"address\":\"Street 1st\"}"
}
"""
let data = jsonStr.data(using: .utf8)
if let d = data {
do {
let o = try JSONSerialization.jsonObject(with: d)
print(o)
} catch let e {
print(e)
}
} else {
print("DATA conversion ERROR")
}
First of all if you wrap the JSON in the literal string syntax of Swift 4 you have to escape the backslashes.
let jsonStr = """
{
"status": "success",
"data": "{\\"name\\":\\"asd\\",\\"address\\":\\"Street 1st\\"}"
}
"""
You got nested JSON. The value for key data is another JSON string which must be deserialized separately
let jsonData = Data(jsonStr.utf8)
do {
if let object = try JSONSerialization.jsonObject(with: jsonData) as? [String:String] {
print(object)
if let dataString = object["data"] as? String {
let dataStringData = Data(dataString.utf8)
let dataObject = try JSONSerialization.jsonObject(with: dataStringData) as? [String:String]
print(dataObject)
}
}
} catch {
print(error)
}
Or – with a bit more effort but – much more comfortable with the (De)Codable protocol
struct Response : Decodable {
private enum CodingKeys : String, CodingKey { case status, data }
let status : String
let person : Person
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String.self, forKey: .status)
let dataString = try container.decode(String.self, forKey: .data)
person = try JSONDecoder().decode(Person.self, from: Data(dataString.utf8))
}
}
struct Person : Decodable {
let name, address : String
}
let jsonStr = """
{
"status": "success",
"data": "{\\"name\\":\\"asd\\",\\"address\\":\\"Street 1st\\"}"
}
"""
let jsonData = Data(jsonStr.utf8)
do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch {
print(error)
}
You have an error in your code ,as you write it in code like that """json""",
also
data is String not dictionary , so you will need to convert to data then JSONSerialization again
check code i add your response in Json file and parse it , and work correctly
So just write this in json file called it data.json
{
"status": "success",
"data": "{\"name\":\"asd\",\"address\":\"Street 1st\"}"
}
and use this :
guard let jsonFile = Bundle.main.path(forResource: "data", ofType: "json") else { return}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: jsonFile), options: .mappedIfSafe) else {return}
if let response = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves) {
print(response)
if let dataInDictionary = response as? [String:Any] , let addresData = dataInDictionary["data"] as? String {
if let jsonData = addresData.data(using: .utf8),
let dictionary = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableLeaves) as? [String:Any]{
print(dictionary)
}
}
}
Here is another example based on the answer #vadian
Swift 4 - Using Codable
This was the json that I received:
{
"error_code": 0,
"result": {
"responseData": "{\"emeter\":{\"get_realtime\":{\"voltage_mv\":237846,\"current_ma\":81,\"power_mw\":7428,\"total_wh\":1920,\"err_code\":0}}}"
}
}
The JSON part with backslashes is equal to this:
{
"emeter": {
"get_realtime": {
"voltage_mv": 237846,
"current_ma": 81,
"power_mw": 7428,
"total_wh":19201,
"err_code":0
}
}
}
And this was the code that I used:
import Foundation
class RealtimeEnergy: Codable {
let errorCode: Int
let result: ResultRealtimeEnergy?
let msg: String?
enum CodingKeys: String, CodingKey {
case errorCode = "error_code"
case result, msg
}
init(errorCode: Int, result: ResultRealtimeEnergy?, msg: String?) {
self.errorCode = errorCode
self.result = result
self.msg = msg
}
}
class ResultRealtimeEnergy: Codable {
let responseData: String
var emeter: Emeter
enum CodingKeys: String, CodingKey {
case responseData
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
responseData = try container.decode(String.self, forKey: .responseData)
let dataString = try container.decode(String.self, forKey: .responseData)
emeter = try JSONDecoder().decode(Emeter.self, from: Data(dataString.utf8))
}
}
class Emeter: Codable {
let emeter: EmeterClass
init(emeter: EmeterClass) {
self.emeter = emeter
}
}
class EmeterClass: Codable {
let getRealtime: GetRealtime
enum CodingKeys: String, CodingKey {
case getRealtime = "get_realtime"
}
init(getRealtime: GetRealtime) {
self.getRealtime = getRealtime
}
}
class GetRealtime: Codable {
let voltageMv, currentMa, powerMw, totalWh: Int
let errCode: Int
enum CodingKeys: String, CodingKey {
case voltageMv = "voltage_mv"
case currentMa = "current_ma"
case powerMw = "power_mw"
case totalWh = "total_wh"
case errCode = "err_code"
}
init(voltageMv: Int, currentMa: Int, powerMw: Int, totalWh: Int, errCode: Int) {
self.voltageMv = voltageMv
self.currentMa = currentMa
self.powerMw = powerMw
self.totalWh = totalWh
self.errCode = errCode
}
}