create Dictionary class - ios

I am coding a Swift class that builds a dictionary when instantiating it, plus the possibility of adding pairs of key - value, but once done the problem is I can not access the individual pairs I add, so there must be a problem somewhere I can not detect, please help
class MyDictionary {
var dictionary = ["":""]
init() {
}
func addPair(key: String, value: String) {
//dictionary["key"] = "value"
dictionary.updateValue(value, forKey: key)
}
}
// just to compare... this one works
var dict = ["one": "abc"]
dict["two"] = "efg"
print(dict["two"]!)
let myDictionary = MyDictionary()
myDictionary.addPair(key: "key1", value: "value1")
print(myDictionary)
//print(myDictionary["key1"]) //Does not work

Change
print(myDictionary["key1"])
To
print(myDictionary.dictionary["key1"])

I a assuming you want to create a custom dictionary, because you'd like to check (maybe in future) if the values to be added as as a key-pair are acceptable or so.
Anyway, if you want to have a custom dictionary class to act as a wrapper over the system Dictionary class, I'd advice to make the dictionary property private (so no outsiders can access and modify it directly), and add an additional method for getting the value for a key (if such key exists):
class MyDictionary {
private var dictionary = ["":""]
init() {
}
func addPair(key: String, value: String) {
dictionary.updateValue(value, forKey: key)
}
func valueForKey(_ key: String) -> String? {
if let valueForKey = dictionary[key] {
return valueForKey
}
return nil
}
}
This is how would you use it:
let myDictionary = MyDictionary()
myDictionary.addPair(key: "key1", value: "value1")
if let valueForKey = myDictionary.valueForKey("key1") {
print(valueForKey)
}
If you really want to get the value the way you mentioned it's not working, you'll need to implement a subscript, because MyDictionary is a custom class and does not have one yet:
class MyDictionary {
private var dictionary = ["":""]
/* more code */
func valueForKey(_ key: String) -> String? {
if let valueForKey = dictionary[key] {
return valueForKey
}
return nil
}
subscript(_ key: String) -> String? {
get {
return valueForKey(key)
}
}
}
This is how would you use the subscript:
let myDictionary = MyDictionary()
myDictionary.addPair(key: "key1", value: "value1")
if let valueForKey = myDictionary["key1"] {
print(valueForKey)
print(myDictionary["key1"]!)
}
Note that myDictionary["key1"] returns an Optional String, or String?, therefore you'd have to force unwrap the value in order for the print() method not to complain. Since we do it inside the if let statement - we know myDictionary["key1"] is not nil, and myDictionary["key1"]! must be some valid String object.
See more here: https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html

To be able to use your syntax add key subscription support (getter and setter).
class MyDictionary {
var dictionary = ["":""]
init() {
}
func addPair(key: String, value: String) {
dictionary.updateValue(value, forKey: key)
}
subscript(_ key: String) -> String? {
get { return dictionary[key] }
set { dictionary[key] = newValue }
}
}
let myDictionary = MyDictionary()
myDictionary.addPair(key: "key1", value: "value1")
print(myDictionary["key1"]!)
You could extend the class as generic to use it with different types. The subscription setter makes addPair even redundant.
class MyDictionary<K : Hashable, V> {
var dictionary = [K:V]()
subscript(_ key: K) -> V? {
get { return dictionary[key] }
set { dictionary[key] = newValue }
}
}
let myDictionary = MyDictionary<String,String>()
myDictionary["key1"] = "value1"
print(myDictionary["key1"]!)

Related

How to insert different type of element in array

I have a class that has a reference to another class. A student class has a variable that has a reference to module info. I want to insert the moduleId into an array but the struct is made up of different types of objects and is of type moduleRef. I would like to get the moduleId which is of type string. How can I grab this moduleId element form ModuleRef.
class Student {
var firstName: String
var lastName: String
var modulesRefs: [ModuleRef]
var fullName: String {
return self.firstName + " " + self.lastName
}
init(firstName: String, lastName: String, modulesRefs: [ModuleRef]) {
self.firstName = firstName
self.lastName = lastName
self.modulesRefs = modulesRefs
}
func addModuleId(_ moduleId: String) {
self.modulesRefs.insert(moduleId, at: 0)
}
func removeModuleId(_ moduleId: String) {
self.modulesRefs = self.modulesRefs.filter { $0.moduleId != moduleId }
}
}
class ModuleRef {
var moduleName: String
var moduleId: String
var moduleStartdate: Int
init(moduleName: String, moduleId: String, moduleStartdate: Int) {
self.moduleName = moduleName
self.moduleId = moduleId
self.moduleStartdate = moduleStartdate
}
}
Cannot convert value of type 'String' to expected argument type 'ModuleRef'
You can insert the values of Type ModuleRef into that array. So i may modify the functions as:
func addModuleId(_ moduleId: String) {
let module = ModuleRef(moduleName: "", moduleId: moduleId, moduleStartdate: 0)
self.modulesRefs.insert(module, at: 0)
}
func removeModuleId(_ moduleId: String) {
modulesRefs.removeAll(where: { $0.moduleId == moduleId })
}
NB: If you don't need moduleName & moduleStartdate all time, you can mark it as Optional.
In your code you have made class ModuleRef { ... } values compulsory and you are just passing a single value moduleId and you are adding it to an Array of ModuleRef which is totally wrong approach. So, based on what you have said here is the updated code of your code:
class Student {
var firstName: String
var lastName: String
var modulesRefs: [ModuleRef]
var fullName: String {
return self.firstName + " " + self.lastName
}
init(firstName: String, lastName: String, modulesRefs: [ModuleRef]) {
self.firstName = firstName
self.lastName = lastName
self.modulesRefs = modulesRefs
}
func addModuleId(_ moduleId: String) {
// This will be your updated intializer where you can pass any value you want and other will be taken as default value
let moduleRef = ModuleRef(moduleId: moduleId)
self.modulesRefs.insert(moduleRef, at: 0)
}
func removeModuleId(_ moduleId: String) {
self.modulesRefs = self.modulesRefs.filter { $0.moduleId != moduleId }
}
}
class ModuleRef {
var moduleName: String
var moduleId: String
var moduleStartdate: Int
/// Here you can provide all the default values which you don't want to pass
init(moduleName: String = "", moduleId: String = "", moduleStartdate: Int = 0) {
self.moduleName = moduleName
self.moduleId = moduleId
self.moduleStartdate = moduleStartdate
}
}
Besides, you can also pass the nil value for all making the variable as optional. Let me know, if you have any confusion!
You cannot insert a ModuleRef just by passing a String.
You need to create an instance for example adding the full name of the student and the UNIX timestamp of the current date
func addModuleId(_ moduleId: String) {
let moduleRef = ModuleRef(moduleName: self.fullName,
moduleId: moduleId,
moduleStartDate: Int(Date().timeIntervalSince1970))
self.modulesRefs.insert(moduleRef, at: 0)
}
And to remove an object this is more efficient, however the class must conform to Equatable
func removeModuleId(_ moduleId: String) {
guard let index = self.modulesRefs.firstIndex(where: {$0.moduleId == moduleId}) else { return }
self.modulesRefs.remove(at: index)
}
I'd even declare moduleStartDate as Date.
You need to updated your Array type to Any as follows:
var modulesRefs: [Any]
While access value from array,
if let moduleRef = modulesRefs[index] as? ModuleRef {
} else if let str = modulesRefs[index] as? String {
}
What you're trying to do, actually means that there's a flaw in your design.
If you need the functionality to call student.addModuleId(moduleId) that means, that Student should not hold an array of ModuleRef. (Or at least have a mechanism to fetch the whole ModuleRef by it's id, and then insert that ModuleRef inside the array)
That being said, After figuring out your design to which will it be: modules: [ModuleRef] or moduleIds: [String], there are still somethings that you can get use out of:
Equatable protocol. If you make ModuleRef to conform to Equatable protocol, and check its equality with id, then Swift will give you access to remove(element:_) method, and you no longer need the removeModuleId() method.
Or if all ModuleIds are unique, you can implement Hashable protocol as well, and use Set<ModuleRef> instead of an array to have O(1) remove and insert methods. (right now, remove function, takes O(n) time)
You need to make an array of Any type..
var modulesRefs: [Any] = []
You can add element in array like this...
modulesRefs.append("Test string")
let obj = ModuleRef(...)
modulesRefs.append(obj)
And when you access an element you need to user either if let or guard let to check its type with safety.
For eg.
for obj in modulesRefs {
if let strType = obj as? String {
print("\(obj) is string type")
} else if let moduleRefObj = obj as? ModuleRef {
print("\(obj) is ModuleRef type")
}

What is the proper way to use EVReflection to parse a Dictionary type?

I am using EVReflection in my app. One JSON response should be parsed as type Dictionary<String,Array<MyObject>>. I have successfully parsed this by overriding the setValue method like this:
override func setValue(_ value: Any!, forUndefinedKey key: String) {
switch key {
case "response":
if let dict = value as? NSDictionary {
response = Dictionary<String,Array<MyObject>>();
for (key, value) in dict {
var listValues = Array<MyObject>();
if let array = value as? NSArray {
for vd in array {
listValues.append(MyObject(dictionary: vd as! NSDictionary));
}
}
response![key as? String ?? ""] = listValues;
}
}
break;
}
}
However, I am seeing the following error in the console:
ERROR: Could not create an instance for type Swift.Dictionary<Swift.String, Swift.Array<MyObject>>
Is there a different way I should be doing this? How do I get the error to go away?
I was able to figure this out by using a propertyConverter as follows:
override public func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
return[
(
key: "response"
, decodeConverter: {
if let dict = $0 as? NSDictionary {
self.response = Dictionary<String,Array<MyObject>>();
for (key, value) in dict {
var listValues = Array<MyObject>();
if let array = value as? NSArray {
for vd in array {
listValues.append(MyObject(dictionary: vd as! NSDictionary));
}
}
self.response![key as? String ?? ""] = listValues;
}
}
}
, encodeConverter: { return nil }
)
]
}
With EVReflection you should be using NSDictionary not a Dictionary (which is a struct).
If you do this then you shouldn't need to override any property converter methods.

How to use multiple key for the same value in an Swift Dictionary?

How can I store user records into Swift Dictionary, but index them by two type of keys: userID and firstName? Is it possible?
If a value is removed both expression users[aUserID] and user[aFirstName] should return nil.
Dictionaries don't care about unique values, they only care about unique keys. So you can have as many identical values in a dictionary as you like, as long as their keys are all different.
The way I would approach your problem is by hiding all the necessary logic to keep the dictionary keys in sync in a wrapper class:
struct User {
let userId: Int
let firstName: String
// more user properties
}
class UserDict {
private var dict = [String: User]()
subscript(key: String) -> User? {
return dict[key]
}
subscript(key: Int) -> User? {
return dict["\(key)"]
}
func add(_ user: User) {
dict[user.firstName] = user
dict["\(user.userId)"] = user
}
func remove(forKey key: String) {
if let user = dict[key] {
dict.removeValue(forKey: user.firstName)
dict.removeValue(forKey: "\(user.userId)")
}
}
func remove(forKey key: Int) {
remove(forKey: "\(key)")
}
var count: Int {
return dict.count / 2
}
}
You can have multiple keys for the same value, consider this example:
class Value {
var name = "John"
}
var dictionary: [String: Value] = [:]
var value1 = Value()
dictionary["key1"] = value1
dictionary["key2"] = value1
value1.name = "Theresa"
dictionary["key1"]?.name // "Theresa"
dictionary["key2"]?.name // "Theresa"
For the second part of your question:
If value is removed both expression users[aUserID] and user[aFirstName] should return nil.
When you do this dict["key1"] = nil, the value for the key2 is still there and would be value1, so you could create a custom subscript that would handle that by finding identical values in the dictionary.

Turn swift object into a JSON string

I have classes like these:
class MyDate
{
var year : String = ""
var month : String = ""
var day : String = ""
init(year : String , month : String , day : String) {
self.year = year
self.month = month
self.day = day
}
}
class Lad
{
var firstName : String = ""
var lastName : String = ""
var dateOfBirth : MyDate?
init(firstname : String , lastname : String , dateofbirth : MyDate) {
self.firstName = firstname
self.lastName = lastname
self.dateOfBirth = dateofbirth
}
}
class MainCon {
func sendData() {
let myDate = MyDate(year: "1901", month: "4", day: "30")
let obj = Lad(firstname: "Markoff", lastname: "Chaney", dateofbirth: myDate)
let api = ApiService()
api.postDataToTheServer(led: obj)
}
}
class ApiService {
func postDataToTheServer(led : Lad) {
// here i need to json
}
}
And I would like to turn a Lad object into a JSON string like this:
{
"firstName":"Markoff",
"lastName":"Chaney",
"dateOfBirth":
{
"year":"1901",
"month":"4",
"day":"30"
}
}
EDIT - 10/31/2017: This answer mostly applies to Swift 3 and possibly earlier versions. As of late 2017, we now have Swift 4 and you should be using the Encodable and Decodable protocols to convert data between representations including JSON and file encodings. (You can add the Codable protocol to use both encoding and decoding)
The usual solution for working with JSON in Swift is to use dictionaries. So you could do:
extension Date {
var dataDictionary {
return [
"year": self.year,
"month": self.month,
"day": self.day
];
}
}
extension Lad {
var dataDictionary {
return [
"firstName": self.firstName,
"lastName": self.lastName,
"dateOfBirth": self.dateOfBirth.dataDictionary
];
}
}
and then serialize the dictionary-formatted data using JSONSerialization.
//someLad is a Lad object
do {
// encoding dictionary data to JSON
let jsonData = try JSONSerialization.data(withJSONObject: someLad.dataDictionary,
options: .prettyPrinted)
// decoding JSON to Swift object
let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])
// after decoding, "decoded" is of type `Any?`, so it can't be used
// we must check for nil and cast it to the right type
if let dataFromJSON = decoded as? [String: Any] {
// use dataFromJSON
}
} catch {
// handle conversion errors
}
If you just need to do this for few classes, providing methods to turn them into dictionaries is the most readable option and won't make your app noticeably larger.
However, if you need to turn a lot of different classes into JSON it would be tedious to write out how to turn each class into a dictionary. So it would be useful to use some sort of reflection API in order to be able to list out the properties of an object. The most stable option seems to be EVReflection. Using EVReflection, for each class we want to turn into json we can do:
extension SomeClass: EVReflectable { }
let someObject: SomeClass = SomeClass();
let someObjectDictionary = someObject.toDictionary();
and then, just like before, we can serialize the dictionary we just obtained to JSON using JSONSerialization. We'll just need to use object.toDictionary() instead of object.dataDictionary.
If you don't want to use EVReflection, you can implement reflection (the ability to see which fields an object has and iterate over them) yourself by using the Mirror class. There's an explanation of how to use Mirror for this purpose here.
So, having defined either a .dataDictionary computed variable or using EVReflection's .toDictionary() method, we can do
class ApiService {
func postDataToTheServer(lad: Lad) {
//if using a custom method
let dict = lad.dataDictionary
//if using EVReflection
let dict = lad.toDictionary()
//now, we turn it into JSON
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict,
options: .prettyPrinted)
// send jsonData to server
} catch {
// handle errors
}
}
}
May this GitHub code will help you.
protocol SwiftJsonMappable {
func getDictionary() -> [String: Any]
func JSONString() -> String
}
extension SwiftJsonMappable {
//Convert the Swift dictionary to JSON String
func JSONString() -> String {
do {
let jsonData = try JSONSerialization.data(withJSONObject: self.getDictionary(), options: .prettyPrinted)
// here "jsonData" is the dictionary encoded in JSON data
let jsonString = String(data: jsonData, encoding: .utf8) ?? ""
// here "decoded" is of type `Any`, decoded from JSON data
return jsonString
// you can now cast it with the right type
} catch {
print(error.localizedDescription)
}
return ""
}
//Convert Swift object to Swift Dictionary
func getDictionary() -> [String: Any] {
var request : [String : Any] = [:]
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if let lable = child.label {
//For Nil value found for any swift propery, that property should be skipped. if you wanna print nil on json, disable the below condition
if !checkAnyContainsNil(object: child.value) {
//Check whether is custom swift class
if isCustomType(object: child.value) {
//Checking whether its an array of custom objects
if isArrayType(object: child.value) {
if let objects = child.value as? [AMSwiftBase] {
var decodeObjects : [[String:Any]] = []
for object in objects {
//If its a custom object, nested conversion of swift object to Swift Dictionary
decodeObjects.append(object.getDictionary())
}
request[lable] = decodeObjects
}
}else {
//Not an arry, and custom swift object, convert it to swift Dictionary
request[lable] = (child.value as! AMSwiftBase).getDictionary()
}
}else {
request[lable] = child.value
}
}
}
}
return request
}
//Checking the swift object is swift base type or custom Swift object
private func isCustomType(object : Any) -> Bool {
let typeString = String(describing: type(of: object))
if typeString.contains("String") || typeString.contains("Double") || typeString.contains("Bool") {
return false
}
return true
}
//checking array
private func isArrayType(object : Any) -> Bool {
let typeString = String(describing: type(of: object))
if typeString.contains("Array"){
return true
}
return false
}
//Checking nil object
private func checkAnyContainsNil(object : Any) -> Bool {
let value = "\(object)"
if value == "nil" {
return true
}
return false
}
}
https://github.com/anumothuR/SwifttoJson

Create an Array of Object from an Array of Dictionaries with Swift

I'm receiving a JSON dictionary from a web service and I need to map the return values to existing values. Here's essentially what I'm trying to do:
class Contract {
var contractID: String?
var ebState: String?
var ibState: String?
var importerState: String?
var exportersBankRefNo: String?
var importersBankRefNo: String?
}
let contract1 = Contract()
contract1.contractID = "001"
let contract2 = Contract()
contract2.contractID = "002"
// This is the JSON return dictionary
let exportAppnStatusList: [[String: String]] = [["contractID":"001",
"ExporterBankRefNo":"ExporterBankRefNo001",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"",
"ImporterBankState":"UNKNOWN",
"ImporterState":"UNKNOWN" ],
["contractID":"002",
"ExporterBankRefNo":"ExporterBankRefNo002",
"ExporterBankState":"ACCEPTED",
"ImporterBankRefNo":"ImporterBankRefNo002",
"ImporterBankState":"ACCEPTED",
"ImporterState":"UNKNOWN" ]]
I need to take the exportAppnStatusList and fill in the associated values in the existing contract1 and contract2, mapping by the contractID
This fills the contracts with available information, it ignores contracts where the id could not be found:
for contract in [contract1, contract2] {
if let contractDict = exportAppnStatusList.filter({$0["contractID"] == contract.contractID}).first {
contract.exportersBankRefNo = contractDict["ExporterBankRefNo"]
contract.ebState = contractDict["ExporterBankState"]
contract.importersBankRefNo = contractDict["ImporterBankRefNo"]
contract.ibState = contractDict["ImporterBankState"]
contract.importerState = contractDict["ImporterState"]
}
}
Why not generate the contract object by mapping over the array of dictionaries like this? You'll need to write a custom initializer that takes all these params
exportAppnStatusList.map { (dict:[Stirng:String]) -> Contract in
return Contract(contractID:dict["contractID"],
ebState:dict["ExporterBankState"],
ibState:dict["ImporterBankState"],
importerState:dict["ImporterState"],
exportersBankRefNo:dict["ExporterBankRefNo"],
importersBankRefNo:dict["ImporterBankRefNo"]
}
Try using this init (your class must inherit from NSObject):
init(jsonDict: [String: String]) {
super.init()
for (key, value) in jsonDict {
if class_respondsToSelector(Contract.self, NSSelectorFromString(key)) {
setValue(value, forKey: key)
}
}
}
Then you can do this:
exportAppnStatusList.forEach {
print(Contract(jsonDict: $0))
}

Resources