let jsonResult = try JSONSerialization.jsonObject(with: jsonData!, options: .mutableContainers) as! NSDictionary
When debugger comes on above line, in debug console there is ->
jsonResult =(NSDictionary) 2 key/value pairs
>[0] = “CompanyList” : 140 elements
>[1] = “StatusTable” : 1 element
jsonResult contains 2 array
Now i wanna to traverse CompanyList using Loop
like
let arr_CompanyList = [CompanyList]()
for dictionary in json as! [[CompanyList]]
{
//arr_CompanyList.append(dictionary)
}
but is gives error
Here is CompanyList Class
public class CompanyList {
public var companyAlt_Key : Int?
public var company_Name : String?
public var tableName : String?
}
How should i do?
You cannot convert your JSON Array response to directly your Class objects array, you need to create your custom class object from the JSON response. Also instead of using NSDictionary in swift use native type Dictionary.
if let jsonResult = (try? JSONSerialization.jsonObject(with: jsonData!, options: [])) as? [String:Any] {
if let companyList = jsonResult["CompanyList"] as? [[String:Any]] {
//Now loop through the companyList array
let arr_CompanyList = companyList.flatMap(CompanyList.init)
//To get array of companyname
let companyNames = companyList.flatMap { $0["Company_Name"] as? String }
print(companyNames)
}
}
Now simply add one init with your CompanyList class like this way.
public class CompanyList {
public var companyAlt_Key : Int?
public var company_Name : String?
public var tableName : String?
init?(dictionary: [String:Any]) {
guard let companyAltKey = dictionary["CompanyAlt_Key"] as? Int,
let companyName = dictionary["Company_Name"] as? String,
let tableName = dictionary["TableName"] as? String else {
return nil
}
self.companyAlt_Key = companyAltKey
self.company_Name = companyName
self.tableName = tableName
}
}
Note: Inside init? method with dictionary you need to access your key that contains value according to your class property.
Related
I have a question concerning modeling JSON data.
I am trying to figure out how to decode JSON exchange rate data. The JSON file consists of a date, base currency ("eur" in the example below), and some currencies with rates compared to the base currency. It would be a straight forward nested JSON object without the base currency, but with the base currency thrown in the middle without a key I'm not sure where to begin.
Do I need codingKeys for the base currency and rates? How do you handle the missing base currency key?
extension MoneyRates: Decodable {
enum CodingKeys: String, CodingKey {
case base = ??
case rates = ??
}
}
Here is some Xcode Playground sample code:
let json =
"""
{
"date": "2021-03-12",
"eur": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
}
}
""".data(using: .utf8)
class MoneyRates: Decodable {
let date: String
let base: String
var rates: [String: Double] = [:]
}
Honestly for this particular JSON I'd use traditional JSONSerialization
struct MoneyRates {
let date: String
let base: String
let rates: [String: Double]
}
let json =
"""
{
"date": "2021-03-12",
"eur": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
}
}
"""
do {
let result = try JSONSerialization.jsonObject(with: Data(json.utf8)) as! [String:Any]
var keys = Array(result.keys)
if let dateIndex = keys.firstIndex(of: "date"),
let date = result[keys[dateIndex]] as? String, keys.count == 2 {
keys.remove(at: dateIndex)
let base = keys.first!
let rates = MoneyRates(date: date, base: base, rates: result[base] as! [String:Double])
print(rates)
}
} catch {
print(error)
}
However, getting an error message that says that my class 'Expenses' does not conform to protocol 'Decodable' & Type 'Expenses' does not conform to protocol 'Encodable'
import Foundation
class Expenses : ObservableObject, Codable {
#Published var items : [ExpenseItem] {
// Step 1 creat did set on publsihed var.
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
init() {
if let items = UserDefaults.standard.data(forKey: "Items") {
let decoder = JSONDecoder(
if let decoded = try?
decoder.decode([ExpenseItem].self, from: items) {
self.items = decoded
return
}
}
self.items = []
}
}
my expense item is flagged as
struct ExpenseItem : Identifiable, Codable {
let id = UUID()
let name : String
let type : String
let amount : Int
}
Conformance to Encodable/Decodable is auto-synthesized when all stored properties conform to Encodable/Decodable, but using a property wrapper on a property means that now the property wrapper type needs to conform to Encodable/Decodable.
#Published property wrapper doesn't conform. It would have been nice to just implement conformance on the Published type itself, but unfortunately it doesn't expose the wrapped value, so without using reflection (I've seen suggestions online), I don't think it's possible.
You'd need to implement the conformance manually:
class Expenses : ObservableObject {
#Published var items : [ExpenseItem]
// ... rest of your code
}
extension Expense: Codable {
enum CodingKeys: CodingKey {
case items
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.items, forKey: .items)
}
required init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
self.items = try container.decode([ExpenseItem].self, forKey: .items)
}
}
This is a similar approach to Save dictionary to UserDefaults, however, it is intended for SwiftUI, not using a single line like set, so I want to store the value somewhere with a variable so I can call it easily. Also it's different because I'm asking for an initialization.
I have the following:
#Published var mealAndStatus: Dictionary
init() {
mealAndStatus = ["Breakfast": "initial", "Snack": "notSet", "Lunch": "notSet", "Snack2": "notSet", "Dinner": "notSet"]
if let storedDay = UserDefaults.standard.value(forKey: "mealAndStatus") {
mealAndStatus = storedDay as! Dictionary
}
}
1- How do I correctly store that dictionary in UserDefaults in SwiftUI?
2- That init, do I have to call it at the beginning of ContentView? Or can I leave it on the other swift file like that? Not sure how the init gets called.
I already made one with bool working:
#Published var startDay: Bool
init() {
startDay = true
if let storedDay = UserDefaults.standard.value(forKey: "startDay") {
startDay = storedDay as! Bool
}
}
but the dictionary doesn't seem to work. I need to initialize that dictionary and also store it in UserDefaults so I can access it later. Any help is appreciated.
This is the perfect solution I found for SwiftUI:
Store this somewhere, in my case I created a class just for UserDefaults:
#Published var mealAndStatus: [String: Date] =
UserDefaults.standard.dictionary(forKey: "mealAndStatus") as? [String: Date] ?? [:] {
didSet {
UserDefaults.standard.set(self.mealAndStatus, forKey: "mealAndStatus")
}
}
That above initializes the dictionary and also creates a variable to be easily called and use to update the value. This can be modified at lunch time and add new values, that way is initialized with whatever I want.
Furthermore, now on Apple Dev wwdc20 they announced a new way of handling UserDefaults with SwiftUI which may be even better than the above. The propery wrapper is called: #AppStorage.
Using JSONEncoder and JSONDecoder would help you convert to data any struct or dictionary that conforms to codable.
let arrayKey = "arrayKey"
func store(dictionary: [String: String], key: String) {
var data: Data?
let encoder = JSONEncoder()
do {
data = try encoder.encode(dictionary)
} catch {
print("failed to get data")
}
UserDefaults.standard.set(data, forKey: key)
}
func fetchDictionay(key: String) -> [String: String]? {
let decoder = JSONDecoder()
do {
if let storedData = UserDefaults.standard.data(forKey: key) {
let newArray = try decoder.decode([String: String].self, from: storedData)
print("new array: \(newArray)")
return newArray
}
} catch {
print("couldn't decode array: \(error)")
}
return nil
}
// You would put this where you want to save the dictionary
let mealAndStatus = ["Breakfast": "initial", "Snack": "notSet", "Lunch": "notSet", "Snack2": "notSet", "Dinner": "notSet"]
store(dictionary: mealAndStatus, key: arrayKey)
// You would put this where you want to access the dictionary
let savedDictionary = fetchDictionay(key: arrayKey)
On a side note, you probably shouldn't be using standard defaults for storing stuff like this. Storing it as a database, or saving it in a file especially with encryption on eith the database or the file might be a bit safer.
I'm making a to-do list app to learn the ins and outs and it has a simple lists-with-items structure. I use 3 classes to manage them:
TodoManager: is a singleton that is meant to centralise managing lists and items in those lists in my view controllers. It holds an array of TodoLists and a bunch of functions to add lists, mark them as completed and return lists.
TodoList: has a string var (name), bool var (completed) and an array of TodoItems
TodoItem: has a string var (name) and a bool var (completed).
I want to store my array of custom objects [TodoList] so I can load it later and I was looking for the simplest way in the world to do it. UserDefaults does not allow custom objects (as it shouldn't because it's for settings) so I need to persist the data using NSCoding and for that I need to have my TodoList class inherit from NSObjects.
class TodoManager: NSObject, NSCoding {
// the singleton
static let shared = TodoManager()
// filePath var
private var filePath : String {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return url.appendingPathComponent("objectsArray").path
}
// init
override private init() {}
// array to store all lists
private var lists = [TodoList]()
func newTodoList(title: String) {
lists.append(TodoList(instanceTitle: title))
}
// coding
func encode(with coder: NSCoder) {
coder.encode(lists, forKey: "lists")
}
required convenience init(coder decoder: NSCoder) {
self.init()
lists = decoder.decodeObject(forKey: "lists") as! [TodoList]
}
// saving and loading
func saveAll() {
let data = lists
NSKeyedArchiver.archiveRootObject(data, toFile: filePath)
}
func loadAll() {
if let dataArray = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? [TodoList] {
lists = dataArray
}
}
}
class TodoList: NSObject, NSCoding {
// array to store items in this list instance
private var items = [TodoItem]()
// vars to store title and completion status
private var title = String()
private var completed = Bool()
// init
init(instanceTitle: String) {
title = instanceTitle
completed = false
}
// coding
func encode(with coder: NSCoder) {
coder.encode(items, forKey: "lists")
coder.encode(title, forKey: "title")
coder.encode(completed, forKey: "completed")
}
required convenience init(coder decoder: NSCoder) {
self.items = decoder.decodeObject(forKey: "items") as! [TodoItem]
self.title = decoder.decodeObject(forKey: "title") as! String
self.completed = decoder.decodeBool(forKey: "completed")
self.init() // <----- critical line
}
// item-related
func addItem(title: String) {
items.append(TodoItem(instanceTitle: title))
}
}
class TodoItem: NSObject, NSCoding {
private var title = String()
private var completed = Bool()
// inits
init(instanceTitle: String) {
title = instanceTitle
completed = false
}
func encode(with coder: NSCoder) {
coder.encode(title, forKey: "title")
coder.encode(completed, forKey: "completed")
}
required convenience init(coder decoder: NSCoder) {
self.init() // <----- similar critical line
title = decoder.decodeObject(forKey: "title") as! String
completed = decoder.decodeBool(forKey: "completed")
}
}
The problem I run into is that instanceTitle is undeclared when in the convenience init so I can't pass it to self.init(). I cannot add the declaration to the required convenience init because it will error that the class does not conform to the required protocol. I tried a good many variations but after hours of staring at this and using my google-foo I can't figure it out. What am I doing wrong in NSCoding's rabbit hole?
When the convenience initializer() is called, the actual arguments originally passed to the designated init() by the function that is requesting a new instance of the class to be made do not need to pass through the convenience init. You call the designated initializer with a placeholder which is filled in on execution. For my case it looks like this:
init(instanceTitle: String) {
title = instanceTitle
completed = false
}
required convenience init(coder decoder: NSCoder) {
// call designated init
self.init(instanceTitle: "[placeholder]")
items = decoder.decodeObject(forKey: "items") as! [TodoItem]
title = decoder.decodeObject(forKey: "title") as! String
completed = decoder.decodeBool(forKey: "completed")
}
I get a web API response, which I loop through and initialize a Customer class for each JSON object in the array. The Customer class has a base class with a couple optional properties. I get errors when there are null values in the JSON, and I am not sure how to handle them properly. I am especially unsure of how to handle a null Date in swift. If anyone could give me some advice, I would really appreciate it!
JSON returned from Web API:
[{"Id":1,"BusinessId":1,"CompanyName":"Test Company4","FirstName":"Mike","LastName":"Doe","CustomerType":1,"CustomerStatus":1,"IsDeleted":false,"Created":"2016-12-22T20:12:10.2760144Z","CreatedBy":1,"Modified":"2016-12-29T19:29:26.1245219Z","ModifiedBy":1},{"Id":2,"BusinessId":1,"CompanyName":"Test Company5","FirstName":"Mike","LastName":"Doe","CustomerType":1,"CustomerStatus":1,"IsDeleted":false,"Created":"2017-01-03T20:18:51.7639708Z","CreatedBy":1,"Modified":null,"ModifiedBy":null}]
Customer class:
class Customer : BaseEntity {
var Id: Int64
var BusinessId: Int64
var CustomerType: Int64
var CustomerStatus: Int64
var CompanyName: String
var FirstName: String
var LastName: String
init?(json: [String: Any]) {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
guard let id = json["Id"] as? Int64,
let businessId = json["BusinessId"] as? Int64,
let customerType = json["CustomerType"] as? Int64,
let customerStatus = json["CustomerStatus"] as? Int64,
let companyName = json["CompanyName"] as? String,
let firstName = json["FirstName"] as? String,
let lastName = json["LastName"] as? String,
let isDeleted = json["IsDeleted"] as? Bool
else {
return nil
}
var created = Date()
if let cdate = json["Created"] as? String {
created = formatter.date(from: cdate)!
}
let createdBy = json["CreatedBy"] as? String
var modified = Date()
if let mdate = json["Modified"] as? String {
modified = formatter.date(from: mdate)!
}
let modifiedBy = json["ModifiedBy"] as? String
self.Id = id
self.BusinessId = businessId
self.CustomerType = customerType
self.CustomerStatus = customerStatus
self.CompanyName = companyName
self.FirstName = firstName
self.LastName = lastName
super.init(isDeleted: isDeleted, created: created, createdBy: createdBy, modified: modified, modifiedBy: modifiedBy)
}
}
BaseEntity class that contains some optional properties:
class BaseEntity {
var IsDeleted: Bool
var Created: Date
var CreatedBy: String
var Modified: Date?
var ModifiedBy: String?
init(isDeleted: Bool, created: Date, createdBy: String, modified: Date, modifiedBy: String) {
self.IsDeleted = isDeleted
self.Created = created
self.CreatedBy = createdBy
self.Modified = modified
self.ModifiedBy = modifiedBy
}
}
One of the problems I am running into is when I initialize Customer, my Customer object looks like this. Then when I try to insert it into a database, it does not recognize the BaseEntity properties as properties of Customer:
Id = 1
BusinessId = 1
CustomerType = 1
CustomerStatus = 1
CompanyName = "Test Company4"
FirstName = "Mike"
LastName = "Doe"
BaseEntity
IsDeleted = true
Created = '1/1/2017'
CreatedBy = 1
Modified = '1/1/2017'
ModifiedBy = 1
You need to define your class type for model
class BaseEntity {
to
class BaseEntity: NSObject {
This is the way you can create your model class: -
//Model Class
class BaseModel: NSObject {
var name: String
var address: String
var mobilenumber: Int
init(name: String?, address: String?, mobilenumber: Int?) {
self.name = name ?? ""
self.address = address ?? ""
self.mobilenumber = mobilenumber ?? 0
}
}
//Pass value inside model class
class ViewController: UIViewController {
var model = [BaseModel]() //Model Array Initialization here
override func viewDidLoad() {
super.viewDidLoad()
//Pass value inside Model
model.append(BaseModel(name: "Your name", address: "Your address", mobilenumber: 5545545452))
}
}
//Get value from Model class
class DetailsController: UIViewController {
var details: BaseModel?
override func viewDidLoad() {
super.viewDidLoad()
//Retrive value from model
let name = details?.name ?? ""
let address = details?.address ?? ""
let mobile = details?.mobilenumber ?? 0
}
}