How to map Object inside Array inside Object in Swift and Alamofire - ios

I'm using Swift, Alamofire, and Alamofire ObjectMapper. I have a response JSON with an array of objects inside. I need to map each of the objects inside the array to an object and put it in an array of objects.
I'm pretty much sure that its's a dumb question, but I haven't found any solution online.
Thank you

One way to do it is to use SwiftyJSON https://github.com/SwiftyJSON/SwiftyJSON
Then you can do something like this.. response is the json reponse from alamofire
var categories = [Category]()
if let json = response["sports"].array {
for var i = 0; i < json.count; i++ {
let category : JSONObj = json[i]
if let catname = category["name"].string {
categories.append(Category(json: json[i]))
}
}
}
In the Category model i have a init method that take a json as parameter.. Like this
class Category {
var name : String?
var id : Int?
init(json : JSONObj) {
if let name = json["name"].string {
self.name = name
}
if let id = json["id"].int {
self.id = id
}
}
init() { }
}

Related

How to Decode JSON with Dynamic key from FirebaseDatabase

I'm trying to use JSONDecoder with FirebaseDatabase. There are some examples online, but I haven't been able to find one with dynamic object keys like the ones that are being returned in the response I am getting. Is there a better way to do this?
JSON Example:
{
"-LUOhAM5W42t9YxqFkUQ" = {
author = {
name = testName;
};
commentCount = 0;
detail = "";
id = "-LUOhAM5W42t9YxqFkUQ";
likeCount = 0;
};
"-LUOhNU4wfwTlZQkrGFu" = {
author = {
name = otherName;
};
commentCount = 0;
detail = "";
id = "-LUOhNU4wfwTlZQkrGFu";
likeCount = 0;
};
My Struct looks like this (I'm just trying to decode successfully first):
struct Post: Decodable {
var id: String?
var title: String?
}
And my code in my controller looks like this:
func getPosts() {
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
print("VALUE: \(value)")
do {
let person = try FirebaseDecoder().decode(Post.self, from: value)
print(person)
} catch let error {
print(error)
}
})
}
I know I could loop through each object, decode and then add them to an array, but I'm looking for the most efficient approach and I really like the JSONDecoder. Again, the snag seems to be that Firebase puts the object key at the beginning of each object, which seems weird. Any help at all would be greatly appreciated!

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))
}

Why array returns nil after appending to it elements?

I'm trying to work with JSON as MVC model, for this I did:
// Country.swift
import SwiftyJSON
class Country {
var code: String!
var dialCode: Int!
var name: String!
init(json: JSON) {
for i in 0...json["countries"].count - 1 {
if let code = json["countries"][i]["code2"].string, dialCode = json["countries"][i]["dialCode"].string, name = json["countries"][i]["name"].string {
self.code = code
self.dialCode = Int(dialCode)
self.name = name
}
}
}
}
and later in my ViewController I do:
var countries = [Country]()
Alamofire.request(.POST, "\(property.host)\(property.getCountryList)", parameters: parameters, encoding: .JSON).responseJSON { response in
do {
let json = JSON(data: response.data!)
countries.append(Country(json: json))
} catch _ {
}
}
but I have a problem. When I print values in Country.swift file, I get results, but when I print(countries) it returns me [Project.Country] and count returns 1. What is the problem? What I do wrong?
Unless I've misunderstood is this not your desired behaviour?
countries is an array of Project.Country, which swift is representing by printing [Project.Country] (an array containing one instance of your class). There is no issue. If you want to prove that the array contains a Project.Country you should print one of the class' properties: print(countries.first.name)
EDIT: problem is you are passing a JSON array of countries to a single init method, which is just setting the properties of itself for every country and not creating an instance for each. Hence you only have one instance returned
Your problem is you are passing countries array to init method which is called only once you must do it like here
class Country {
var code: String!
var dialCode: Int!
var name: String!
init(json: JSON) {
if let code = json["code2"].string, dialCode = json["dialCode"].string, name = json["name"].string {
self.code = code
self.dialCode = Int(dialCode)
self.name = name
}
}
}
And loop here
Alamofire.request(.POST, "", parameters: nil, encoding: .JSON).responseJSON { response in
if let jsonResponse = response.result.value{
let json = JSON(jsonResponse)
for countriesJSON in json["countries"].arrayValue{
self.countries.append(Country(json: countriesJSON))
}
print(self.countries.count)
}
}

How can I store a Dictionary with RealmSwift?

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)...

Adding elements to array from another class

So I have this class in City.swift:
class City {
class Entry {
let name : String
init(name : String) {
self.name = name
}
}
let cities = []
}
And in another file I want to add to an array like this:
var city = City()
city.cities = City(name: "Test")
And I want to be able to call it by indexPath.row number (because I am using it in cellForRowAtIndexPath) like this:
let entry: AnyObject = city.cities[indexPath.row]
println(entry.name as String)
How could I make this code work?
First of all, a few comments.
There is no need for nested classes or even a custom class at all
Just use an array of Strings
add to array like this : array.append(Item)
Do not initialise as AnyObject in a language that is aware of types. (let entry: AnyObject = city.cities[indexPath.row])
In your example you have a collection of Strings so I would just use this:
var cities : [String] = []
cities.append("NY")
let city = cities[0] // NY
You also stated that you have a part of your code in a different file. I am assuming you want to have a method for fetching and storing the values. And a separate method to display them?
I would propose making two classes if you want to handle more than just a City Name and want to have access to the fetched data anywhere in your app.
Google Singleton for more info on how this works.
class City {
var name : String = ""
var geoLocation : CLLocationCoordinate2D?
// more attributes of your city object
init(name name_I: String) {
self.name = name_I
}
}
class Locations {
// the static part is shared between all instances of the Locations Class -> Google Singleton
static var cachedLocations : Locations = Locations()
var cities : [City] = []
init() {
}
}
// add a new city
let newCity = City(name: "NY")
Locations.cachedLocations.cities.append(newCity)
// in another class / file / viewcontroller ,.... get the cached city
let cachedCity = Locations.cachedLocations.cities[0]
You could add a class function to Locations to convert between a Dictionary and the City class. How to get a Dictionary from JSON
// class function is available without needing to initialise an instance of the class.
class func addCachedCity(dictionary:[String:AnyObject]) {
guard let cityName = dictionary["name"] as? String else {
return
}
let newCity = City(name: cityName)
cachedLocations.cities.append(newCity)
}
This would be used like this:
let cityDict : [String:AnyObject] = ["name":"LA"]
Locations.addCachedCity(cityDict)
Locations.cachedLocations.cities // now holds NY and LA
You could simply make your array an global array. You only have to define
your array out of the class. And then use the .append() function
let cities = []
class City {
class Entry {
let name : String
init(name : String) {
self.name = name
}
}
}

Resources