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))
}
Related
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"]!)
So far I have developed android apps with java and Firebase. Now I want to code my first iOS app with Firebase as well. I have this Database Model:
I want to retrieve these Objects and store them in an array. My ExercisePresets Model looks like this:
class ExercisePreset {
var key : String?
var name : String?
var type : String = "Dynamic";
var progressions : [String] = [String]();
//#Exclude
var selected : Bool = false;
init() { }
convenience init(name : String, type : String) {
self.init();
self.name = name;
self.type = type;
}
}
In Java I could just get the snapshot as an Object of my Model like this:
fb.getRefUserPresets(null).addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
ExcersisePresets e = dataSnapshot.getValue(ExcersisePresets.class);
e.setKey(dataSnapshot.getKey());
presets.add(e);
}
}
but I don't get how this works for swift. I tried this:
refPresets!.observe(.childAdded) { (snapshot) in
let e = snapshot.value as! ExercisePreset;
}
But I get this error:
Could not cast value of type '__NSDictionaryM' (0x109d594f8) to 'TrainingsLog.ExercisePreset' (0x106a30408).
So how do I retrieve my Objects the right way?
You have to cast it as NSDictionary
refPresets!.observe(.childAdded) { (snapshot) in
let e = snapshot.value as! NSDictionary
print(e)
}
You have to cast your snapshot.value as NSDictionary
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.
With Swift is it possible to create a dictionary of [String:[Object]] from an array of objects [Object] using a property of those objects as the String key for the dictionary using swift's "map"?
class Contact:NSObject {
var id:String = ""
var name:String = ""
var phone:String = ""
init(id:String, name:String, phone:String){
self.id = id
self.name = name
self.phone = phone
}
}
var contactsArray:[Contact]
var contactsDict:[String:Contact]
contactsDict = (contactsArray as Array).map { ...WHAT GOES HERE... }
Let's say you want to use id as the key for the dictionary:
var contactsArray = [Contact]()
// add to contactsArray
var contactsDict = [String: Contact]()
contactsArray.forEach {
contactsDict[$0.id] = $0
}
The difference between map and forEach is that map returns an array. forEach doesn't return anything.
You can achieve this via reduce in a one-line functional-style code:
let contactsDict = contactsArray.reduce([String:Contact]()) { var d = $0; d[$1.id] = $1; return d; }
This also keeps contactsDict immutable, which is the preferred way to handle variables in Swift.
Or, if you want to get fancy, you can overload the + operator for dictionaries, and make use of that:
func +<K,V>(lhs: [K:V], rhs: Contact) -> [K:V] {
var result = lhs
result[rhs.0] = rhs.1
return result
}
let contactsDict = contacts.reduce([String:Contact]()) { $0 + ($1.id, $1) }
Swift 4
There's now a direct way to do this:
https://developer.apple.com/documentation/swift/dictionary/3127163-init
It's an initializer for Dictionary that lets you return a string key for each element in a Collection that specifies how it should be grouped in the resulting Dictionary.
Considering the following model:
class Person: Object {
dynamic var name = ""
let hobbies = Dictionary<String, String>()
}
I'm trying to stock in Realm an object of type [String:String] that I got from an Alamofire request but can't since hobbies has to to be defined through let according to RealmSwift Documentation since it is a List<T>/Dictionary<T,U> kind of type.
let hobbiesToStore: [String:String]
// populate hobbiestoStore
let person = Person()
person.hobbies = hobbiesToStore
I also tried to redefine init() but always ended up with a fatal error or else.
How can I simply copy or initialize a Dictionary in RealSwift?
Am I missing something trivial here?
Dictionary is not supported as property type in Realm.
You'd need to introduce a new class, whose objects describe each a key-value-pair and to-many relationship to that as seen below:
class Person: Object {
dynamic var name = ""
let hobbies = List<Hobby>()
}
class Hobby: Object {
dynamic var name = ""
dynamic var descriptionText = ""
}
For deserialization, you'd need to map your dictionary structure in your JSON to Hobby objects and assign the key and value to the appropriate property.
I am currently emulating this by exposing an ignored Dictionary property on my model, backed by a private, persisted NSData which encapsulates a JSON representation of the dictionary:
class Model: Object {
private dynamic var dictionaryData: NSData?
var dictionary: [String: String] {
get {
guard let dictionaryData = dictionaryData else {
return [String: String]()
}
do {
let dict = try NSJSONSerialization.JSONObjectWithData(dictionaryData, options: []) as? [String: String]
return dict!
} catch {
return [String: String]()
}
}
set {
do {
let data = try NSJSONSerialization.dataWithJSONObject(newValue, options: [])
dictionaryData = data
} catch {
dictionaryData = nil
}
}
}
override static func ignoredProperties() -> [String] {
return ["dictionary"]
}
}
It might not be the most efficient way but it allows me to keep using Unbox to quickly and easily map the incoming JSON data to my local Realm model.
I would save the dictionary as JSON string in Realm. Then retrive the JSON and convert to dictionary. Use below extensions.
extension String{
func dictionaryValue() -> [String: AnyObject]
{
if let data = self.data(using: String.Encoding.utf8) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
return json!
} catch {
print("Error converting to JSON")
}
}
return NSDictionary() as! [String : AnyObject]
} }
and
extension NSDictionary{
func JsonString() -> String
{
do{
let jsonData: Data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
return String.init(data: jsonData, encoding: .utf8)!
}
catch
{
return "error converting"
}
}
}
UPDATE 2021
Since Realm 10.8.0, it is possible to store a dictionary in a Realm object using the Map type.
Example from the official documentation:
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var currentCity = ""
// Map of city name -> favorite park in that city
let favoriteParksByCity = Map<String, String>()
}
Perhaps a little inefficient, but works for me (example dictionary from Int->String, analogous for your example):
class DictObj: Object {
var dict : [Int:String] {
get {
if _keys.isEmpty {return [:]} // Empty dict = default; change to other if desired
else {
var ret : [Int:String] = [:];
Array(0..<(_keys.count)).map{ ret[_keys[$0].val] = _values[$0].val };
return ret;
}
}
set {
_keys.removeAll()
_values.removeAll()
_keys.appendContentsOf(newValue.keys.map({ IntObj(value: [$0]) }))
_values.appendContentsOf(newValue.values.map({ StringObj(value: [$0]) }))
}
}
var _keys = List<IntObj>();
var _values = List<StringObj>();
override static func ignoredProperties() -> [String] {
return ["dict"];
}
}
Realm can't store a List of Strings/Ints because these aren't objects, so make "fake objects":
class IntObj: Object {
dynamic var val : Int = 0;
}
class StringObj: Object {
dynamic var val : String = "";
}
Inspired by another answer here on stack overflow for storing arrays similarly (post is eluding me currently)...