parse JSON and saving data to the realm is it possible? - json

struct NewsModel: Codable{
let id: Int?
let title, newsModelDescription: String?
let sourceID, version: String?
let publishedAt: Int
let readablePublishedAt: String?
let updatedAt: Int
let readableUpdatedAt: String
let images: Images
let embedTypes: String?
let typeAttributes: TypeAttributes
let type: String?
let source: String?
enum CodingKeys: String, CodingKey {
case id, title
case newsModelDescription
case sourceID
case version, publishedAt, readablePublishedAt, updatedAt, readableUpdatedAt, embedTypes, images,typeAttributes, type, source
}
}
// MARK: - Images
struct Images: Codable {
let square140: String
enum CodingKeys: String, CodingKey {
case square140 = "square_140"
}
}
struct TypeAttributes: Codable {
let imageLarge: String
}
This is my Model. I can successfully parse them and show them on UITableViewCell but I am unable to save them to the realm because these are struct. For save to realm I need to convert them to class and Realm object. But how I convert them to nested class. I want to use the same model to parse and saving data to the realm is it possible?

There are probably 100 different solutions. One option is to just make the object a Realm object that's conforms to the codable protocol. Something like this (not tested: so more of a conceptual solution)
class NewsModel: Object, Codable {
#objc dynamic var _id = UUID().uuidString
#objc dynamic var title = ""
#objc dynamic var news = ""
private enum CodingKeys: String, CodingKey {
case _id
case title
case news
}
override class func primaryKey() -> String? {
return "_id"
}
public required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self._id = try container.decode(String.self, forKey: ._id)
self.name = try container.decode(String.self, forKey: .title)
self.logo = try container.decode(String.self, forKey: .news)
}
or change the model in the question to a class and add a function to save a realm object with the data. Again, not tested so this is just conceptual.
class RealmNewsModel: Object {
#objc dynamic var _id = ""
#objc dynamic var title = ""
#objc dynamic var news = ""
}
class NewsModel, Codable {
let _id: String?
let title: String?
let news: String?
func saveToRealm {
let news = RealmNewsModel()
news._id = self._id
news.title = self.title
news.news = self.news
try! realm.write {
realm.add(news)
}

Related

I can't pull Json data while doing MVVM design project with swift

I am making a project in Swift with MVVM design. I want to get coin name, current price, Rank and Symbol from a Crypto site. I can't show the json data I get on the console. The model is in another folder because I did it with MVVM. How can I create a struct to get the data here? You can find screenshots of my project below. I would be glad if you help.
Below are the codes I wrote in my web service file
import Foundation
class WebService {
func downloadCurrencies(url: URL, completion: #escaping ([DataInfo]?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
} else if let data = data {
let cryptoList = try? JSONDecoder().decode([DataInfo].self, from: data)
print(cryptoList)
if let cryptoList = cryptoList {
completion(cryptoList)
}
}
}
.resume()
}
}
Below are the codes I wrote in my model file
import Foundation
struct DataInfo : Decodable {
var name: String
var symbol: String
var cmc_rank: String
var usd: Double
}
Finally, here is the code I wrote to print the data in the viewController to my console. But unfortunately I can't pull the data.
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?start=1&limit=10&convert=USD&CMC_PRO_API_KEY=5ac24b80-27a1-4d01-81bd-f19620533480")!
WebService().downloadCurrencies(url: url) { cryptos in
if let cryptos = cryptos {
print(cryptos)
}
}
}
I've seen your URL and tested it ON Postman and also i've got your code and tried to put it in a shape, the code is good, but it can't MAP JSON Data against your struct because this is the json Data from Postman
Postman Data
While Looking at your Struct, It Doesnt Match the format of JSON Data you're receiving,
Struct
Well, To Make your structure match the JSON String you need to create Nested String: Any Dictionary. But there's another issue with the logic, you need to decode data outside of the webservice call because it can contain errors which wont be mapped in the struct and can handle if you get other statusCode.
If you try to implement all these things manually, the code will become complex and hard to understand. I would rather recommend you to use Alamofire with SwiftyJSON and it can make your work a lot shorter and easier and understandable.
Sorry for the bad english.
Your api-key is not valid for me.
Your data must be inside of object or invalid keys and you are surely missing it thats why it is not parsing correctly.
My suggestion is to put your json response in this website
"https://app.quicktype.io/"
and replace your struct with new one, you will be good to go hopefully.
Your models does not have 1 to 1 correspondence with the response object. The root object is not a [DataInfo], but another structure that contains an array of DataInfos. Here are the correct models
struct Response: Codable {
let status: Status
let data: [CurrencyData]
}
struct Status: Codable {
let creditCount: Int
let elapsed: Int
let timestamp: String
let totalCount: Int
let errorCode: Int?
let errorMessage: String?
let notice: String?
enum CodingKeys: String, CodingKey {
case notice
case timestamp
case elapsed
case creditCount = "credit_count"
case errorCode = "error_code"
case errorMessage = "error_message"
case totalCount = "total_count"
}
}
enum Currency: String, Codable, Hashable {
case usd = "USD"
}
struct CurrencyData: Codable {
let circulatingSupply: Double?
let cmcRank: Int
let dateAdded: String?
let id: Int
let lastUpdated: String?
let maxSupply: Int?
let name: String
let numMarketPairs: Int
let platform: Platform?
let quote: [String: Price]
let selfReportedCirculatingSupply: String?
let selfReportedMarketCap: String?
let slug: String
let symbol: String
let tags: [String]?
let totalSupply: Double
func price(for currency: Currency) -> Double? {
return quote[currency.rawValue]?.price
}
enum CodingKeys: String, CodingKey {
case id
case name
case platform
case quote
case slug
case symbol
case tags
case circulatingSupply = "circulating_supply"
case cmcRank = "cmc_rank"
case dateAdded = "date_added"
case lastUpdated = "last_updated"
case maxSupply = "max_supply"
case selfReportedCirculatingSupply = "self_reported_circulating_supply"
case selfReportedMarketCap = "self_reported_market_cap"
case totalSupply = "total_supply"
case numMarketPairs = "num_market_pairs"
}
}
struct Price: Codable {
let fullyDilutedMarketCap: Double?
let lastUpdated: String?
let marketCap: Double?
let marketCapDominance: Double?
let percentChange1h: Double?
let percentChange24h: Double?
let percentChange30d: Double?
let percentChange60d: Double?
let percentChange7d: Double?
let percentChange90d: Double?
let price: Double?
let volume24h: Double?
let volumeChange24h: Double?
enum CodingKeys: String, CodingKey {
case price
case fullyDilutedMarketCap = "fully_diluted_market_cap"
case lastUpdated = "last_updated"
case marketCap = "market_cap"
case marketCapDominance = "market_cap_dominance"
case percentChange1h = "percent_change_1h"
case percentChange24h = "percent_change_24h"
case percentChange30d = "percent_change_30d"
case percentChange60d = "percent_change_60d"
case percentChange7d = "percent_change_7d"
case percentChange90d = "percent_change_90d"
case volume24h = "volume_24h"
case volumeChange24h = "volume_change_24h"
}
}
struct Platform: Codable {
let id: Int
let name: String
let symbol: String
let slug: String
let tokenAddress: String?
enum CodingKeys: String, CodingKey {
case id
case name
case symbol
case slug
case tokenAddress = "token_address"
}
}
and you can retrieve the cryptoList in your completion handler like this:
let cryptoList = (try? JSONDecoder().decode([Response].self, from: data))?.data
Also it's not safe to expose your personal data to the internet (API_KEY, etc.)

Type ' ' does not conform to protocol 'Encodable'

Can someone tell what's wrong here please?
It send me this error: Type 'CityWeatherInfo' does not conform to protocol 'Encodable'
struct CityWeatherInfo: Codable {
var name: String
var main: Main
var weathers: [Weather]
private enum CodingKeys: String, CodingKey {
case weathers = "weather"
case main = "main"
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
let mainContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .main)
let weatherContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .weathers)
}
}
struct Weather: Decodable {
var main: String
var description: String
private enum WeatherKeys: String, CodingKey {
case main = "main"
case description = "description"
}
}
struct Main: Decodable {
var temp: Double
var feels_like: Double
var temp_min: Double
var temp_max: Double
private enum MainKeys: String, CodingKey {
case temp = "temp"
case feels_like = "feels_like"
case temp_min = "temp_min"
case temo_max = "temp_max"
}
}
Json is this:
{"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"base":"stations","main":{"temp":287.45,"feels_like":286.61,"temp_min":284.82,"temp_max":289.15,"pressure":1012,"humidity":72},"visibility":10000,"wind":{"speed":1,"deg":0},"clouds":{"all":100},"dt":1592362322,"sys":{"type":1,"id":1414,"country":"GB","sunrise":1592365362,"sunset":1592425222},"timezone":3600,"id":2643743,"name":"London","cod":200}
If a struct or class adopts Codable the required methods init(from decoder: Decoder) and encode(to encoder: Encoder) are synthesized by default.
But if you implement one of the methods yourself you have to implement the other, too. Or – in this case – adopt only Decodable because you are only reading/decoding the data.
There are a few bad practices in your code.
The custom CodingKeys WeatherKeys and MainKeys are pointless because in the default syntax the framework generates the keys named CodingKeys. Custom keys are considered only in a custom implementation.
Both nestedContainers are not needed and they make no sense anyway if they are keyed by the same CodingKeys.
You can omit the CodingKeys if the dictionary keys match the struct member names.
If the values of the struct members are not going to be modified declare them as constants (let).
According to the naming convention variables are supposed to be named lowerCamelCased. A convenient way to translate snake_case to camelCase is to specify the convertFromSnakeCase key decoding strategy.
The openweathermap API provides degrees in Celsius by adding units=metric in the URL or Fahrenheit by adding units=imperial. For example
https://api.openweathermap.org/data/2.5/weather?q=London&units=metric&appid=•••••••••••••"
The UNIX timestamps (1592362322) can be decoded as Date by specifying the secondsSince1970 date decoding strategy
The structs can be created in this simple form
struct CityWeatherInfo: Decodable {
let name: String
let main: Main
let weather: [Weather]
let dt : Date
}
struct Weather: Decodable {
let main: String
let description: String
}
struct Main: Decodable {
let temp, tempMin, tempMax, feelsLike : Double
}
And decode the data with this code
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .secondsSince1970
let weatherInfo = try decoder.decode(CityWeatherInfo.self, from: data)
print(weatherInfo)
} catch {
print(error)
}

Need help decoding Json that contains an array

I need to do the following :
Define two Swift classes to decode the JSON string
Decode the JSON string to get the objects of the two classes
This is the JSON I have to decode :
{“status":200,"holidays":[{"name":"Thanksgiving","date":"2017-10-09","observed":"2017-10-09","public":false}]}
I have tried creating two classes already and all I get back is nothing when calling the class in the main class
class HolidayItems : Decodable {
let name : String?
let date : String?
let observed: String?
let `public` : Bool?
private enum CodingKeys: String, CodingKey {
case name
case date
case observed
case `public`
}
required init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
date = try container.decode(String.self, forKey: .date)
observed = try container.decode(String.self, forKey: .observed)
`public` = try container.decode(Bool.self, forKey: .`public`)
}
} // HolidayItems
class HolidayAPI: Decodable {
let status: HolidayItems
// let holiday :[HolidayItems]
func getHolidayName() -> String {
return status.name ?? "no advice, server problem"
}
func getAdviceNo() -> String {
return status.date ?? ""
}
private enum CodingKeys: String, CodingKey {
case status
case holiday = "items"
}
required init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(HolidayItems.self, forKey: .status)
// holiday = try container.decode(HolidayItems.self, forKey: .holiday)
}
}
This is the result I'm suppose to get :
Optional("Thanksgiving")
Optional("2017-10-09")
and I get nothing in return
Your response is on root level just object with status of type Int and one array of another objects
Note
you don't have to implement your custom CodingKey
you don't need custom init with Decoder
you can have struct for your models
you can rename HolidayItems to Holiday
struct HolidayAPI: Decodable {
let status: Int
let holidays: [Holiday]
}
struct Holiday: Decodable {
let name, date, observed: String
let `public`: Bool
}
Then when you need to get certain holiday item, just get certain element of holidays
decodedResponse.holidays[0].name

How to dynamically add properties to a constant Decodable object in Swift?

Background
Basically I got an api that returns something like this:
"order_detail": [
{
"id": 6938,
"order_id": 6404,
"item_price": "4",
..
"item": {
"id": 12644,
"ref": "Iced Caffe Americano",
"short_description": "",
..
and in my decodable obj i got this
public struct OrderDetail: Decodable {
public let id: Int
public let order_id: Int
public let item_price: String?
..
public let item: Item?
and
public struct Item: Decodable {
public var id: Int
public var ref: String?
public var short_description: String?
The problem is that somewhere else in the code, there is a method that's expecting the Item object to have item_price.
Question
What I want to do is swizzle or mutate this constant Item object and dynamically add item_price property to it.. How can I do that?
Workarounds, other solutions
1. Change json
I know there are many other solutions to this same problem (I'm working on it as we speak, which is simply modifying the api endpoint to suit my needs).. but again that option is not always possible (ie suppose the backend team is separate)
2. Change the function expectation
That is also possible, but also not inexpensive as this function is used in many other places in the app which I don't potentially have control over
If you want to add a property to a Decodable type that's not part of its JSON representation, so simply need to declare a CodingKey conformant type and leave out the specific property name so that the automatically synthesised init(from decoder:Decoder) initialiser will know not to look for that value in the JSON.
Btw you should also conform to the Swift naming convention (lowerCamelCase for variable names) and use CodingKey to map the JSON keys to the property names.
public struct Item: Decodable {
public var id: Int
public var ref: String?
public var shortDescription: String?
public var itemPrice: String? // or whatever else its type needs to be
private enum CodingKeys: String, CodingKey {
case id, ref, shortDescription = "short_description"
}
}
This is one way to achieve this
Take over the initialization of Item in OrderDetail decoding.
struct OrderDetail: Decodable {
let id: Int
let orderId: Int
let itemPrice: String?
let item: Item
private enum OrderDetailCodingKey: CodingKey {
case id
case orderId
case itemPrice
case item
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: OrderDetailCodingKey.self)
self.id = try container.decode(Int.self, forKey: .id)
self.orderId = try container.decode(Int.self, forKey: .orderId)
let itemPrice = try container.decode(String?.self, forKey: .itemPrice)
self.itemPrice = itemPrice
self.item = try Item(from: decoder, itemPrice: itemPrice)
}
}
Use a custom initializer to create your item.
struct Item: Decodable {
let id: Int
let ref: String?
let shortDescription: String?
let itemPrice: String?
private enum ItemCodingKeys: CodingKey {
case id
case ref
case shortDescription
}
init(from decoder: Decoder, itemPrice: String?) throws {
let container = try decoder.container(keyedBy: ItemCodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.ref = try? container.decode(String.self, forKey: .ref)
self.shortDescription = try? container.decode(String.self, forKey: .shortDescription)
self.itemPrice = itemPrice
}
}
You can call the following function to test the functionality:
private func test() {
let json = """
{"id":6938,"orderId":6404,"itemPrice":"4","item":{"id":12644,"ref":"Iced Caffe Americano","shortDescription":""}}
"""
let data = json.data(using: .utf8)
let decoder = JSONDecoder()
if let data = data {
do {
let order = try decoder.decode(OrderDetail.self, from: data)
print(order)
} catch let jsonError {
os_log("JSON decoding failed [%#]", String(describing: jsonError))
}
} else {
os_log("No data found")
}
}

What parsing object when property can be integer or bool?

Sometimes server sends me property as bool (true, false).
Sometimes server sends me property as an integer (0,1).
How can I decodable this case via standard Decodable in Swift 4?
Example.
I have:
final class MyOffer : Codable {
var id = 0
var pickupAsap: Int?
enum CodingKeys: String, CodingKey {
case id
case pickupAsap = "pickup_asap"
}
}
Responses from server are:
1) "pickup_all_day": true,
2) "pickup_all_day": 0
you may implement your own decode init method, get each class property from decode container, during this section, make your logic dealing with wether "asap" is an Int or Bool, sign all required class properties at last.
here is a simple demo i made:
class Demo: Decodable {
var id = 0
var pickupAsap: Int?
enum CodingKeys: String, CodingKey {
case id
case pickupAsap = "pickup_asap"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(Int.self, forKey: .id)
let pickupAsapBool = try? container.decode(Bool.self, forKey: .pickupAsap)
let pickupAsapInt = try? container.decode(Int.self, forKey: .pickupAsap)
self.pickupAsap = pickupAsapInt ?? (pickupAsapBool! ? 1 : 0)
self.id = id
}
}
mock data:
let jsonInt = """
{"id": 10,
"pickup_asap": 0
}
""".data(using: .utf8)!
let jsonBool = """
{"id": 10,
"pickup_asap": true
}
""".data(using: .utf8)!
test:
let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Demo.self, from: jsonInt)
print("asap with Int: \(result.pickupAsap)")
let result2 = try! jsonDecoder.decode(Demo.self, from: jsonBool)
print("asap with Bool: \(result2.pickupAsap)")
output:
asap with Int: Optional(0)
asap with Bool: Optional(1)
for more info: Apple's encoding and decoding doc