swift api json numeric unit comma control - json

func getKoreacountryChart() {
AF.request("https://api.corona-19.kr/korea/country/new/?", headers: headers).responseJSON { response in
let result = response.data
if result != nil {
let json = JSON(result!)
let seoul = json["seoul"]["totalCase"].intValue
let busan = json["busan"]["totalCase"].intValue
let daegu = json["daegu"]["totalCase"].intValue
let incheon = json["incheon"]["totalCase"].intValue
let gwangju = json["gwangju"]["totalCase"].intValue
let daejeon = json["daejeon"]["totalCase"].intValue
let ulsan = json["ulsan"]["totalCase"].intValue
let sejong = json["sejong"]["totalCase"].intValue
let gyeonggi = json["gyeonggi"]["totalCase"].intValue
let gangwon = json["gangwon"]["totalCase"].intValue
let chungbuk = json["chungbuk"]["totalCase"].intValue
let chungnam = json["chungnam"]["totalCase"].intValue
let jeonbuk = json["jeonbuk"]["totalCase"].intValue
let jeonnam = json["jeonnam"]["totalCase"].intValue
let gyeongbuk = json["gyeongbuk"]["totalCase"].intValue
let gyeongnam = json["gyeongnam"]["totalCase"].intValue
let jeju = json["jeju"]["totalCase"].intValue
let quarantine = json["quarantine"]["totalCase"].intValue
self.KoreaCountryData = KCountryData(seoul: seoul, busan: busan, daegu: daegu, incheon: incheon, gwangju: gwangju, daejeon: daejeon, ulsan: ulsan, sejong: sejong, gyeonggi: gyeonggi, gangwon: gangwon, chungbuk: chungbuk, chungnam: chungnam, jeonbuk: jeonbuk, jeonnam: jeonnam, gyeongbuk: gyeongbuk, gyeongnam: gyeongnam, jeju: jeju, quarantine: quarantine)
} else {
self.KoreaCountryData = KcountrytestDate
}
}
struct KCountryData {
let seoul: Int
let busan: Int
let daegu: Int
let incheon: Int
let gwangju: Int
let daejeon: Int
let ulsan: Int
let sejong: Int
let gyeonggi: Int
let gangwon: Int
let chungbuk: Int
let chungnam: Int
let jeonbuk: Int
let jeonnam: Int
let gyeongbuk: Int
let gyeongnam: Int
let jeju: Int
let quarantine: Int
}
I'm using Swiftui to create a covid app.
I got a json form from api and I was pasing
"totalCase": "26,732",
"recovered": "24,395", The json format contains a comma, so it is not accurate output.
ex) totalCase: 26, recovered: 24 I want to erase the comma and express all the numbers.
{
"resultCode": "0",
"resultMessage": "정상 처리되었습니다.",
"korea": {
"countryName": "합계",
"newCase": "97",
"totalCase": "26,732",
"recovered": "24,395",
"death": "468",
"percentage": "51.56",
"newCcase": "79",
"newFcase": "18"
},
"seoul": {
"countryName": "서울",
"newCase": "25",
"totalCase": "6,081",
"recovered": "5,500",
"death": "78",
"percentage": "62.47",
"newCcase": "20",
"newFcase": "5"
}

You can get your string remove the commas (thousand separator) and the enclosing quotes as well. One you have done that you can decode your json string and treat the properties as integers. You can still use SwiftyJSON if you would like to or using Codable as shown bellow:
struct Root: Codable {
let resultCode: Int
let resultMessage: String
let korea, seoul: Country
}
struct Country: Codable {
let countryName: String
let newCase, totalCase, recovered, death: Int
let percentage: Double
let newCcase, newFcase: Int
}
if let result = response.data {
do {
let cleaned = result.replacingOccurrences(of: ",(?=\\d{3})", with: "", options: .regularExpression)
.replacingOccurrences(of: #"\"(\d+)\""#, with: "$1", options: .regularExpression)
.replacingOccurrences(of: #"\"(\d{1,2}.\d{1,2})\""#, with: "$1", options: .regularExpression)
print(cleaned) // { "resultCode": 0, "resultMessage": "정상 처리되었습니다.", "korea": { "countryName": "합계", "newCase": 97, "totalCase": 26732, "recovered": 24395, "death": 468, "percentage": 51.56, "newCcase": 79, "newFcase": 18 }, "seoul": { "countryName": "서울", "newCase": 25, "totalCase": 6081, "recovered": 5500, "death": 78, "percentage": 62.47, "newCcase": 20, "newFcase": 5 }}
let root = try JSONDecoder().decode(Root.self, from: Data(cleaned.utf8))
print(root) // "Root(resultCode: 0, resultMessage: "정상 처리되었습니다.", korea: __lldb_expr_11.Country(countryName: "합계", newCase: 97, totalCase: 26732, recovered: 24395, death: 468, percentage: 51.56, newCcase: 79, newFcase: 18), seoul: __lldb_expr_11.Country(countryName: "서울", newCase: 25, totalCase: 6081, recovered: 5500, death: 78, percentage: 62.47, newCcase: 20, newFcase: 5))\n"
} catch {
print(error)
}
}

You can use a number formatter to convert this format to an int
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "en_US")
numberFormatter.numberStyle = .decimal
Example
let string = "6,081"
if let value = numberFormatter.number(from: string)?.intValue {
print(value)
}

Related

turning the json result into dynamic

i have a very complicated issue for a beginner. firstly I have this result from json
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
and when I use https://app.quicktype.io to create the object for me it gives this and that is right.
import Foundation
// MARK: - APIResult
struct APIResult {
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [String: Quote]
}
// MARK: - Quote
struct Quote {
let tryeur: Double
}
but I don't want my currencies hardcoded like this so if I choose from: USD to : EUR in my app I want to get the result under Quote as USDEUR. And I also know that if I change anything in this struct it won't work. So how will make those currency selections dynamic to make it work in different currencies. This is a currency converter app and I want to get these rates and reflect it on a chart in my app. Thank you.
Edit: I think I need to get used to using stack overflow properly. Sorry for any inconvenience . At last I could get the dates and rates written in the console. my question is now :
how can i get these results in the console passed into my charts x(dates) and y axis(rates) ?
["2022-12-22": 19.803011, "2022-12-18": 19.734066, "2022-12-23": 19.907873, "2022-12-21": 19.79505, "2022-12-24": 19.912121, "2022-12-17": 19.756527, "2022-12-16": 19.752446, "2022-12-25": 19.912121, "2022-12-19": 19.794356, "2022-12-20": 19.824031]
this is the func i get these
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -9, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
let result: () = currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
print(result)
}
and this is my previously created and hardcoded charts
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
lineChart.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 240)
lineChart.center = view.center
view.addSubview(lineChart)
var entries = [ChartDataEntry]()
for x in 0..<10 {
entries.append(ChartDataEntry(x: Double(x), y: Double(x)))
}
let set = LineChartDataSet(entries: entries)
set.colors = ChartColorTemplates.material()
let data = LineChartData(dataSet: set)
lineChart.data = data
}
Decodable is pretty versatile and highly customizable.
Write a custom initializer and map the quote dictionary to an array of Quote instances which contains the date and the quote. The key TRYEUR is irrelevant and will be ignored.
let jsonString = """
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
"""
struct APIResult: Decodable {
private enum CodingKeys: String, CodingKey {
case success, timeframe, startDate = "start_date", endDate = "end_date", source, quotes
}
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [Quote]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
success = try container.decode(Bool.self, forKey: .success)
timeframe = try container.decode(Bool.self, forKey: .timeframe)
startDate = try container.decode(String.self, forKey: .startDate)
endDate = try container.decode(String.self, forKey: .endDate)
source = try container.decode(String.self, forKey: .source)
let quoteData = try container.decode([String: [String:Double]].self, forKey: .quotes)
quotes = quoteData.compactMap({ (key, value) in
guard let quote = value.values.first else { return nil }
return Quote(date: key, quote: quote)
}).sorted{$0.date < $1.date}
}
}
struct Quote {
let date: String
let quote: Double
}
do {
let result = try JSONDecoder().decode(APIResult.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
I changed my api provider now but this new one is not that much different than the previous one . this is the response I get inside browser
{
"success": true,
"timeseries": true,
"start_date": "2022-04-01",
"end_date": "2022-04-05",
"base": "USD",
"rates": {
"2022-04-01": {
"TRY": 14.686504
},
"2022-04-02": {
"TRY": 14.686504
},
"2022-04-03": {
"TRY": 14.686145
},
"2022-04-04": {
"TRY": 14.696501
},
"2022-04-05": {
"TRY": 14.72297
}
}
}
this is my object
struct APIResult: Codable {
let timeseries: Bool
let success: Bool
let startDate: String
let endDate: String
let base: String
var rates: [String:[String:Double]]
}
and this is my code inside VC to get the current date and 10 days before and I can see it printed in the console.
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = .current
formatter.locale = .current
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -10, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
lastly these are the codes inside my other file called CurrencyManager
func fetchRatesForTimeframe(from: String, to: String, startDate: String, endDate:String) {
let urlString = "\(timeFrameUrl)base=\(from)&symbols=\(to)&start_date=\(startDate)&end_date=\(endDate)&apikey=\(api.convertApi)"
performRequestforTimeframe(with: urlString)
}
func performRequestforTimeframe(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
} else {
if let safeData = data {
if let timeFrameRates = parseJSONForTimeframe(currencyData: safeData) {
print(timeFrameRates)
self.delegate?.didGetTimeframeRates(timeFrameRates)
}
}
}
}
task.resume()
}
}
func cut(_ value: [String: [String: Double]]) -> [String: [String: Double]] {
let dic = value
.sorted(by: { $0.0 < $1.0 })[0..<10] // <-- last 10 results
.reduce(into: [String: [String: Double]]()) {
$0[$1.key] = $1.value
}
return dic
}
func parseJSONForTimeframe(currencyData: Data) -> APIResult? {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let decoder = JSONDecoder()
var jsondata = try decoder.decode(APIResult.self, from: currencyData)
jsondata.rates = cut(jsondata.rates)
return jsondata
} catch {
return nil
}
}
}
why I can't get the result for print(timeFrameRates) inside func performRequestforTimeframe ? what s missing?

How to parse weather data from NASA API

So, the issue is - I am trying to display the Mars weather from the Mars Insight API. Here is a link Insight Weather, the data is returning in JSON format and has three levels. The keys have names that change depending on the current date (sols). How to make the structure of mutable properties?... when the property names change every day. Do we have any instruments to parse such JSON?
{
"815": {
"First_UTC": "2021-03-12T14:54:38Z",
"Last_UTC": "2021-03-13T15:34:09Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 728.378,
"ct": 153082,
"mn": 708.4211,
"mx": 744.9279
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"818": {
"First_UTC": "2021-03-15T20:01:49Z",
"Last_UTC": "2021-03-16T17:32:54Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 727.696,
"ct": 109855,
"mn": 710.223,
"mx": 743.946
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"819": {
....
,
"sol_keys": [
"815",
"818",
"819",
"820",
"821"
],
"validity_checks": {
"815": {
"PRE": {
"sol_hours_with_data": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
],
"valid": true
}
}
So, Structure:
import Foundation
struct WeatherData: Decodable {
let season: String?
let pre: pre?
let solKeys: [String]
enum CodingKeys: String, CodingKey {
case season = "season"
case pre = "PRE"
case solKeys = "sol_keys"
}
struct pre: Decodable {
let av: Double?
let mn: Double?
let mx: Double?
enum CodingKeys: String, CodingKey {
case av = "av"
case mn = "mn"
case mx = "mx"
}
}
}
Parsing:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
guard let url = URL(string: urlString) else { return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let safeData = data else { return }
do {
let solWeather = try JSONDecoder().decode([String:WeatherData].self, from: safeData)
if let keys = solWeather["sol_keys"] {
for key in keys { //error: For-in loop requires 'WeatherData' to conform to 'Sequence'
let report = solWeather [key]
}
}
print(solWeather)
}
catch let error {
print(error)
}
}.resume()
}
}
Got an error: For-in loop requires 'WeatherData' to conform to 'Sequence'
Don't understand why :(( Can someone help me?
Thanks!
You cannot decode [String:WeatherData].self because the dictionary contains other values which are not WeatherData, for example the [String] value of sol_keys.
The only way to decode this JSON with JSONDecoder is to implement init(with decoder, decode the sol_keys and create your own temporary CodingKeys to be able to decode the arbitrary dictionary keys.
First declare the custom CodingKey
public struct SolKeys: CodingKey {
public let stringValue: String
public init?(stringValue: String) { self.stringValue = stringValue }
public var intValue: Int? { return nil }
public init?(intValue: Int) { return nil }
}
The Decodable structs are
struct SolData : Decodable {
let firstUTC, lastUTC : Date
let pre : Pre
private enum CodingKeys : String, CodingKey {
case firstUTC = "First_UTC", lastUTC = "Last_UTC", pre = "PRE"
}
}
struct Pre: Decodable {
let av, mn, mx : Double
}
struct WeatherData: Decodable {
let solKeys: [String]
var soldata = [String:SolData]()
enum CodingKeys: String, CodingKey {
case solKeys = "sol_keys"
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.solKeys = try container.decode([String].self, forKey: .solKeys)
let customContainer = try decoder.container(keyedBy: SolKeys.self)
for key in solKeys {
let solKey = SolKeys(stringValue: key)!
let data = try customContainer.decode(SolData.self, forKey: solKey)
soldata[key] = data
}
}
}
And the code to receive and decode the data
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let solWeather = try decoder.decode(WeatherData.self, from: data!)
let keys = solWeather.solKeys
for key in keys {
let report = solWeather.soldata[key]!
print(report)
}
}
catch {
print(error)
}
}.resume()

Error decoding JSON - keyNotFound(CodingKeys

When I try to decode this json:
"polls": [
{
"title": "title",
"date": "date",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
},
}
}, /*<and about 76 of this>*/ ]
with this Codable:
struct poll: Codable {
var stats: stats
var title: String?
var date: String?
var summary: String?
struct stats: Codable {
var total: total
struct total: Codable {
var nays: Int
var yays: Int
var nas: Int
var abstentions: Int
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I get the following error
keyNotFound(CodingKeys(stringValue: "dagegen gestimmt", intValue: nil)(if you need the full error text tell me)
I tried some of the answer from similar questions but nothing worked.
You apparently have occurrences of total where dagegen gestimmt is absent. So, make that an Optional, e.g. Int?:
struct Poll: Codable {
let stats: Stats
let title: String?
let date: Date?
let summary: String?
struct Stats: Codable {
let total: Total
struct Total: Codable {
let nays: Int?
let yays: Int?
let nas: Int?
let abstentions: Int?
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I’d also suggest the following, also reflected in the above:
Start type names (e.g. your struct names) with uppercase letter;
Use let instead of var as we should always favor immutability unless you really are going to be changing these values within this struct; and
If your date is in a consistent format, I’d suggest making the date a Date type, and then you can supply the JSONDecoder a dateDecodingStrategy that matches (see sample below).
For example:
let data = """
{
"polls": [
{
"title": "New Years Poll",
"date": "2019-01-01",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
}
}
},{
"title": "Caesar's Last Poll",
"date": "2019-03-15",
"summary": "summary2",
"stats": {
"total": {
"dafür gestimmt": 42
}
}
}
]
}
""".data(using: .utf8)!
struct Response: Codable {
let polls: [Poll]
}
do {
let decoderDateFormatter = DateFormatter()
decoderDateFormatter.dateFormat = "yyyy-MM-dd"
decoderDateFormatter.locale = Locale(identifier: "en_US_POSIX")
let userInterfaceDateFormatter = DateFormatter()
userInterfaceDateFormatter.dateStyle = .long
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(decoderDateFormatter)
let response = try decoder.decode(Response.self, from: data)
let polls = response.polls
for poll in polls {
print(poll.title ?? "No title")
print(" date:", poll.date.map { userInterfaceDateFormatter.string(from: $0) } ?? "No date supplied")
print(" yays:", poll.stats.total.yays ?? 0)
print(" nays:", poll.stats.total.nays ?? 0)
}
} catch {
print(error)
}
That produces:
New Years Poll
date: January 1, 2019
yays: 454
nays: 139
Caesar's Last Poll
date: March 15, 2019
yays: 42
nays: 0
Set your model as per following format. Also check datatype as per your response.
struct PollsModel:Codable{
var polls : [PollsArrayModel]
enum CodingKeys:String, CodingKey{
case polls
}
struct PollsArrayModel:Codable{
var title : String?
var date : String?
var summary : String?
var stats : PollsStatsModel
enum CodingKeys:String, CodingKey{
case title
case date
case summary
case stats
}
struct PollsStatsModel:Codable{
var total : PollsStatsTotalModel
enum CodingKeys:String, CodingKey{
case total
}
struct PollsStatsTotalModel:Codable{
var dagegen_gestimmt : Int?
var nicht_beteiligt : Int?
var dafür_gestimmt : Int?
var enthalten : Int?
enum CodingKeys:String, CodingKey{
case dagegen_gestimmt = "dagegen gestimmt"
case nicht_beteiligt = "nicht beteiligt"
case dafür_gestimmt = "dafür gestimmt"
case enthalten = "enthalten"
}
}
}
}
}

how to convert JSON to structure type data in swift?

I have an API for my user to login to the database . I am using the following code to send the credentials to the API and if they are valid I am going to get some JSON typed data about the users information . Otherwise I get a string saying that the username or password is wrong .
Here is my HTTTP Post request :
let url = URL(string: "http://128.199.199.17:3000/api/login")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "email=22222#gmail.com&password=123456"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("\(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString!)")
}
task.resume()
It works fine and I get the following information in the console :
{
"user_id": 2,
"email": "22222#gmail.com",
"password": "123456",
"user_name": "number 2 name",
"full_name": "danial kosarifar",
"sex": "male",
"height": 0,
"weight": 0,
"number_of_meal_per_day": 3,
"water_amount": 0,
"calories": 0,
"number_of_hours_to_sleep_per_day": 3,
"createdAt": "2017-11-14T17:23:31.000Z",
"updatedAt": "2017-11-14T17:25:37.000Z"
}
I've also created a decodable structure like so :
struct User : Decodable {
let user_id : Int
let email : String
let password : String
let username : String
}
my question is instead of decoding the data to string how can I decode them in such way that I can put them in the structure thats I've defined . I am completely new to this topic please bear with me if my question too of much of a beginner .
Thanks
Swift 4:
It's better for creating struct as Codable like below,
struct User:Codable { //Because enum CodingKeys: String, CodingKey {
let userID : Int
let email : String
let password : String
let userName : String
let fullName:String
let sex:String
let height:Int
let weight:Int
let numberOfMealPerDay:Int
let waterAmount:Int
let calories:Int
let numberOfHoursToSleppPerDay:Int
let createdAt:String
let updatedAt:String
enum CodingKeys: String, CodingKey {
case userID = "user_id"
case email
case password
case userName = "user_name"
case fullName = "full_name"
case sex
case height
case weight
case numberOfMealPerDay = "number_of_meal_per_day"
case waterAmount = "water_amount"
case calories
case numberOfHoursToSleppPerDay = "number_of_hours_to_sleep_per_day"
case createdAt
case updatedAt
}
}
By using JSONDecoder:
Then response data data inside the closure task can be parsed by using JSONDecoder. Here, user is the class variable. i.e.,var user:User?
if let json = try? JSONDecoder().decode(User.self, from: data!){
self.user = json
}
Note: For better understanding, this is my video series about JSON parsing in swift 4
You can add a custom initializer to your user struct that takes the json data as a parameter and make it throws. You will need also to create a custom date formatter to parse your dates (you need to include the milliseconds to it). Note that if your json response may not include some keys/values you will need to make that property optional:
So for the date formatter you can use this custom date formatter from this answer:
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
}
Your user struct should look like this:
struct User: Codable {
let id: Int
let email: String
let password: String
let userName: String
let fullName: String
let sex: String
let height: Int
let weight: Int
let mealsPerDay: Int
let waterAmount: Int
let calories: Int
let hoursToSleepPerDay: Int
let createdAt: Date
let updatedAt: Date
// you can provide different keys to your user struct properties
private enum CodingKeys: String, CodingKey {
case id = "user_id", email, password, userName = "user_name", fullName = "full_name", sex, height, weight, mealsPerDay = "number_of_meal_per_day", waterAmount = "water_amount", calories, hoursToSleepPerDay = "number_of_hours_to_sleep_per_day", createdAt, updatedAt
}
// custom initializer that takes the json data and throws in case of error
init(data: Data) throws {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)
self = try decoder.decode(User.self, from: data)
}
}
usage:
let data = Data("""
{
"user_id": 2,
"email": "22222#gmail.com",
"password": "123456",
"user_name": "number 2 name",
"full_name": "danial kosarifar",
"sex": "male",
"height": 0,
"weight": 0,
"number_of_meal_per_day": 3,
"water_amount": 0,
"calories": 0,
"number_of_hours_to_sleep_per_day": 3,
"createdAt": "2017-11-14T17:23:31.000Z",
"updatedAt": "2017-11-14T17:25:37.000Z"
}
""".utf8)
do {
let user = try User(data: data)
print(user) // User(id: 2, email: "22222#gmail.com", password: "123456", userName: "number 2 name", fullName: "danial kosarifar", sex: "male", height: 0, weight: 0, mealsPerDay: 3, waterAmount: 0, calories: 0, hoursToSleepPerDay: 3, createdAt: 2017-11-14 17:23:31 +0000, updatedAt: 2017-11-14 17:25:37 +0000)\n"
} catch {
print(error)
}

How to serialize or convert Swift objects to JSON?

This below class
class User: NSManagedObject {
#NSManaged var id: Int
#NSManaged var name: String
}
Needs to be converted to
{
"id" : 98,
"name" : "Jon Doe"
}
I tried manually passing the object to a function which sets the variables into a dictionary and returns the dictionary. But I would want a better way to accomplish this.
In Swift 4, you can inherit from the Codable type.
struct Dog: Codable {
var name: String
var owner: String
}
// Encode
let dog = Dog(name: "Rex", owner: "Etgar")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(dog)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
// Decode
let jsonDecoder = JSONDecoder()
let secondDog = try jsonDecoder.decode(Dog.self, from: jsonData)
Along with Swift 4 (Foundation) now it is natively supported in both ways, JSON string to an object - an object to JSON string.
Please see Apple's documentation here JSONDecoder() and here JSONEncoder()
JSON String to Object
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(myStruct.self, from: jsonData)
Swift Object to JSONString
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(myStruct)
print(String(data: data, encoding: .utf8)!)
You can find all details and examples here Ultimate Guide to JSON Parsing With Swift 4
UPDATE: Codable protocol introduced in Swift 4 should be sufficient for most of the JSON parsing cases. Below answer is for people who are stuck in previous versions of Swift and for legacy reasons
EVReflection :
This works of reflection principle. This takes less code and also supports NSDictionary, NSCoding, Printable, Hashable and Equatable
Example:
class User: EVObject { # extend EVObject method for the class
var id: Int = 0
var name: String = ""
var friends: [User]? = []
}
# use like below
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = User(json: json)
ObjectMapper :
Another way is by using ObjectMapper. This gives more control but also takes a lot more code.
Example:
class User: Mappable { # extend Mappable method for the class
var id: Int?
var name: String?
required init?(_ map: Map) {
}
func mapping(map: Map) { # write mapping code
name <- map["name"]
id <- map["id"]
}
}
# use like below
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = Mapper<User>().map(json)
I worked a bit on a smaller solution that doesn't require inheritance. But it hasn't been tested much. It's pretty ugly atm.
https://github.com/peheje/JsonSerializerSwift
You can pass it into a playground to test it. E.g. following class structure:
//Test nonsense data
class Nutrient {
var name = "VitaminD"
var amountUg = 4.2
var intArray = [1, 5, 9]
var stringArray = ["nutrients", "are", "important"]
}
class Fruit {
var name: String = "Apple"
var color: String? = nil
var weight: Double = 2.1
var diameter: Float = 4.3
var radius: Double? = nil
var isDelicious: Bool = true
var isRound: Bool? = nil
var nullString: String? = nil
var date = NSDate()
var optionalIntArray: Array<Int?> = [1, 5, 3, 4, nil, 6]
var doubleArray: Array<Double?> = [nil, 2.2, 3.3, 4.4]
var stringArray: Array<String> = ["one", "two", "three", "four"]
var optionalArray: Array<Int> = [2, 4, 1]
var nutrient = Nutrient()
}
var fruit = Fruit()
var json = JSONSerializer.toJson(fruit)
print(json)
prints
{"name": "Apple", "color": null, "weight": 2.1, "diameter": 4.3, "radius": null, "isDelicious": true, "isRound": null, "nullString": null, "date": "2015-06-19 22:39:20 +0000", "optionalIntArray": [1, 5, 3, 4, null, 6], "doubleArray": [null, 2.2, 3.3, 4.4], "stringArray": ["one", "two", "three", "four"], "optionalArray": [2, 4, 1], "nutrient": {"name": "VitaminD", "amountUg": 4.2, "intArray": [1, 5, 9], "stringArray": ["nutrients", "are", "important"]}}
This is not a perfect/automatic solution but I believe this is the idiomatic and native way to do such. This way you don't need any libraries or such.
Create an protocol such as:
/// A generic protocol for creating objects which can be converted to JSON
protocol JSONSerializable {
private var dict: [String: Any] { get }
}
extension JSONSerializable {
/// Converts a JSONSerializable conforming class to a JSON object.
func json() rethrows -> Data {
try JSONSerialization.data(withJSONObject: self.dict, options: nil)
}
}
Then implement it in your class such as:
class User: JSONSerializable {
var id: Int
var name: String
var dict { return ["id": self.id, "name": self.name] }
}
Now:
let user = User(...)
let json = user.json()
Note: if you want json as a string, it is very simply to convert to a string: String(data: json, encoding .utf8)
Some of the above answers are completely fine, but I added an extension here, just to make it much more readable and usable.
extension Encodable {
var convertToString: String? {
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
do {
let jsonData = try jsonEncoder.encode(self)
return String(data: jsonData, encoding: .utf8)
} catch {
return nil
}
}
}
struct User: Codable {
var id: Int
var name: String
}
let user = User(id: 1, name: "name")
print(user.convertToString!)
//This will print like the following:
{
"id" : 1,
"name" : "name"
}
Not sure if lib/framework exists, but if you would like to do it automatically and you would like to avoid manual labour :-) stick with MirrorType ...
class U {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
extension U {
func JSONDictionary() -> Dictionary<String, Any> {
var dict = Dictionary<String, Any>()
let mirror = reflect(self)
var i: Int
for i = 0 ; i < mirror.count ; i++ {
let (childName, childMirror) = mirror[i]
// Just an example how to check type
if childMirror.valueType is String.Type {
dict[childName] = childMirror.value
} else if childMirror.valueType is Int.Type {
// Convert to NSNumber for example
dict[childName] = childMirror.value
}
}
return dict
}
}
Take it as a rough example, lacks proper conversion support, lacks recursion, ... It's just MirrorType demonstration ...
P.S. Here it's done in U, but you're going to enhance NSManagedObject and then you'll be able to convert all NSManagedObject subclasses. No need to implement this in all subclasses/managed objects.
struct User:Codable{
var id:String?
var name:String?
init(_ id:String,_ name:String){
self.id = id
self.name = name
}
}
Now just make your object like this
let user = User("1","pawan")
do{
let userJson = try JSONEncoder().encode(parentMessage)
}catch{
fatalError("Unable To Convert in Json")
}
Then reconvert from json to Object
let jsonDecoder = JSONDecoder()
do{
let convertedUser = try jsonDecoder.decode(User.self, from: userJson.data(using: .utf8)!)
}catch{
}
2021 | SWIFT 5.1 | Results solution
Input data:
struct ConfigCreds: Codable {
// some params
}
usage:
// get JSON from Object
configCreds
.asJson()
.onSuccess{ varToSaveJson = $0 }
.onFailure{ _ in // any failure code }
// get object of type "ConfigCreds" from JSON
someJsonString
.decodeFromJson(type: ConfigCreds.self)
.onSuccess { configCreds = $0 }
.onFailure{ _ in // any failure code }
Back code:
#available(macOS 10.15, *)
public extension Encodable {
func asJson() -> Result<String, Error>{
JSONEncoder()
.try(self)
.flatMap{ $0.asString() }
}
}
public extension String {
func decodeFromJson<T>(type: T.Type) -> Result<T, Error> where T: Decodable {
self.asData()
.flatMap { JSONDecoder().try(type, from: $0) }
}
}
///////////////////////////////
/// HELPERS
//////////////////////////////
#available(macOS 10.15, *)
fileprivate extension JSONEncoder {
func `try`<T : Encodable>(_ value: T) -> Result<Output, Error> {
do {
return .success(try self.encode(value))
} catch {
return .failure(error)
}
}
}
fileprivate extension JSONDecoder {
func `try`<T: Decodable>(_ t: T.Type, from data: Data) -> Result<T,Error> {
do {
return .success(try self.decode(t, from: data))
} catch {
return .failure(error)
}
}
}
fileprivate extension String {
func asData() -> Result<Data, Error> {
if let data = self.data(using: .utf8) {
return .success(data)
} else {
return .failure(WTF("can't convert string to data: \(self)"))
}
}
}
fileprivate extension Data {
func asString() -> Result<String, Error> {
if let str = String(data: self, encoding: .utf8) {
return .success(str)
} else {
return .failure(WTF("can't convert Data to string"))
}
}
}
fileprivate func WTF(_ msg: String, code: Int = 0) -> Error {
NSError(code: code, message: msg)
}