I am trying to use CodableFirebase to parse a snapshot.
My Json is like this :
snapshot.value = Optional({
Box = {
Outbox = {
"-LpdzdzdzdzdDs7" = {
DateInvoice = "2019-09-23 10:41:20 +0000";
TrailOrHike = Hike;
Number_of_child = 2;
Children = {
0VZrgdYvV4hyhhyhhy4tyElASvR83 = {
DateVenue = "2019-09-23 10:43:44 +0000";
Age = 3;
"Avatar" = "gs://oooo.appspot.com/Avatar/0V3R4tyElASvR83.png";
};
wQpyIhyhyhyhyh1CI6YBfc3yppX2 = {
"Avatar" = "http://graph.facebook.com/00000/picture?type=large&height=200&width=200";
};
};
In my Outbox, I can have UID (of families)
And in each UID, I can have 0,1 or x children, with data inside.
I would like to parse it correctly but I don't manage to do it with dynamic UID (like -LpdzdzdzdzdDs7), and inside this dynamic UID ,there is more data to parse (Age,DateVenue,Avatar)..
I have no problem to parse other data, but when it's nested I am lost .
My Codable for the rest is simple :
struct CampProfile: Codable {
struct Family : Codable {
let DateVenue : String?
enum CodingKeys : String, CodingKey {
case DateVenue
}
}
let Camp_Name: String
let Camp_Adress: String
}
let model = try FirebaseDecoder().decode(CampProfile.self, from: value)
print(model)
let model2 = try FirebaseDecoder().decode(CampProfile.Family.self, from: value)
print(model2) . //NOTWORKING
Related
I am making a call to an API in Swift and I am using the JSONDecoder. The API call returns categories and ids, the problem I am having, is that it stores the subcategories under the key of the main categories id.
The returned data looks like this:
categories = {
data = {
main = (
{
id = 39;
name = Electronics;
}
)
sub = {
39 =
(
{
id = 49;
name = TV;
}
)
}
}
}
The problem is that 39. In my structure I can't have let 39:[Category] or else I get the error:
Expected patten.
Is there any workaround for this?
You can not use 39 as a parameter name, but you can use this
let category_39: [Category]
private enum CodingKeys : String, CodingKey {
case category_39 = "39"
}
But i think this structure will be difficult, if a new Category added from server side there is no preDefined category id
i suggest to use the following model
struct Model: Codable {
let main: [Category]
let sub: [String: [Category]]
}
struct Category: Codable {
let id: Int
let name: String
}
and then get SubCategories by category id
let subCategories = model.sub[String(id)] // String(id) --> "39"
In realm i saving struct data like this
class DBDialogs: Object {
#objc dynamic var userId = 0
#objc dynamic var initials = ""
#objc private dynamic var structData:Data? = nil
var Info : User? {
get {
if let data = structData {
do {
return try JSONDecoder().decode(User.self, from: data)
}catch {
return nil
}
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
struct User : Codable {
var pk : Int?
var email : String?
var brand : String?
var discs : Discs?
enum CodingKeys: String, CodingKey {
case pk = "pk"
case email = "email"
case brand = "brand"
case discs = "discs"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
pk = try values.decodeIfPresent(Int.self, forKey: .pk)
email = try values.decodeIfPresent(String.self, forKey: .email)
brand = try values.decodeIfPresent(String.self, forKey: .brand)
discs = try values.decodeIfPresent(Discs.self, forKey: .discs)
}
}
how can i filter the struct data ? trying something like this, but i get the error (Unable to parse the format string "Info?.brand == %#")
var dbDialogsArray: Results<DBDialogs>?
var dbDialogsMethods = DBDialogsMethods()
....
class DBDialogsMethods { // also in this class i will write to realm, update, delete and etc
var realm: Realm!
func getArray() -> Results<DBDialogs> {
return realm.objects(DBDialogs.self)
}
}
....
let realm = try! Realm()
dbDialogsMethods.realm = realm
dbDialogsArray = dbDialogsMethods.getArray()
...
let predicate = NSPredicate(format: "Info?.brand = %#", brandField.text!)
let filteredDialogs = dbDialogsArray?.filter(predicate)
i have array DBDialogs, i need to filter the array by fields in Info (structData)
Why i do with NSPredicate :
I have several fields by which I filter data (fields for filtering are selected manually in the application), using NSPredicate I can filter multiple fields that the user chose in the app, something like this
var stringPredicate = Dictionary <String, NSObject> ()
stringPredicate ["brand"] = brand.text! as NSObject
var generalPredicates = [NSPredicate] ()
for (key, value) in stringPredicate {
let predicate = NSPredicate (format: "% K =% #", key, value)
generalPredicates.append (predicate)
}
and filter everything at once, I don’t know how to do filtering in another way with the condition that you need to filter only those fields that the user selected in the application
I am getting the response from API and going to parse response using 'JSONDecoder' and able to parse but I want the sorted array of 'FieldModel' according to 'order' key in "content" object of "ContentModel" auto when I decode using JSONDecoder?
My response from API:
{content = (
{
fieldName = \"$.alcohol.beer\";
label = Beer;
order = 2;
},
{
fieldName = \"$.alcohol.wine\";
label = Wine;
order = 1;
},
{
fieldName = \"$.alcohol.any\”;
label = Wine;
order = 3;
}
);}
My models:
struct ContentModel: Codable {
var content: [FieldModel]?
}
struct FieldModel: Codable {
var fieldName: String?
var order: Int?
var label: String?
}
Code for decode the data:
let myFinalData = try JSONDecoder().decode(ContentModel.self, from: jsonData)
How can I get content of ContentModel will be sorted according to order key after decode using above code?
You can achieve your goals by creating a custom init(from decoder) method for your ContentModel struct, where you use the order property of the FieldModel elements to assign the FieldModels to their respective place in the content array.
I'd also suggest making all field of your structs immutable and non-optional unless you have a good reason to do otherwise.
struct ContentModel: Codable {
let content: [FieldModel]
private enum CodingKeys: String, CodingKey {
case content
}
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let content = try container.decode([FieldModel].self, forKey: .content)
var orderedContent = content
for element in content {
orderedContent[element.order-1] = element
}
self.content = orderedContent
}
}
struct FieldModel: Codable {
let fieldName: String
let label: String
let order: Int
}
I have JSON array like this
var json = NSArray() // array with json objects
//print json >>
json = (
{
Name = "Alen";
Score = 500;
},
{
Name = "John";
Score = 0;
},
{
Name = "Mark";
Score = 2000;
},
{
Name = "Steve";
Score = 300;
},
{
Name = "Ricky";
Score = 900;
}
)
and i can access its objects as
(json[0] as! NSDictionary).object(forKey: "Name")
(json[0] as! NSDictionary).object(forKey: "Score")
I want to sort this JSON array according to scores.
I found the answers like
let sortedArray = json.sorted(by: { $0.0 < $1.0 })
which gives error
Value of type 'Any' has no member '0'
Then I tried this
let sortedArray = (json as! NSDictionary).sorted {(aDic, bDic) -> Bool in
return aDic.key < bDic.key
}
It gave error
Binary operator '<' cannot be applied to two 'Any' operands
Can you please guide me to sort the array according to score in swift 4?
That's a very good example why you are strongly discouraged from using NSArray and NSDictionary in Swift.
Both collection types don't provide type information so everything is treated as Any. Most of the shared generic API of the Swift Standard library cannot be used with Any so you are not able to take advantage of the powerful generic functions unless you add a lot of ugly type casts.
If all values are String declare your array as
var json = [[String:String]]()
Then you can sort the array with
let sortedArray = json.sorted { $0["Score"]! < $1["Score"]! }
The most recommended solution is to decode the JSON directly into a custom struct
struct Player : Decodable {
let name : String
let score : String
private enum CodingKeys : String, CodingKey { case name = "Name", score = "Score" }
}
Then you get rid of all type casting and you can sort by the property name
var players = [Player]()
let jsonString = """
[{"Name" : "Alen", "Score" : "500"},
{"Name" : "John", "Score" : "0"},
{"Name" : "Mark", "Score" : "2000"},
{"Name" : "Steve", "Score" : "300"},
{"Name" : "Ricky", "Score" : "900"}]
"""
let data = Data(jsonString.utf8)
do {
players = try JSONDecoder().decode([Player].self, from: data)
let sortedPlayers = players.sorted{ $0.score.compare($1.score, options: .numeric) == .orderedAscending }
print(sortedPlayers)
} catch { print(error) }
Edit:
To load the JSON use an asynchronous way (URLSession)
Never load data from a remote URL with synchronous Data(contentsOf.
var players = [Player]()
let jsonUrl = URL(string: "url.json")!
let task = URLSession.shared.dataTask(with : url) { [unowned self] (data, _, error) in
if let error = error { print(error); return }
do {
players = try JSONDecoder().decode([Player].self, from: data!).sorted{ $0.score < $1.score }
DispatchQueue.main.async { // reload the table view if necessary
self.tableView.reloadData()
}
} catch { print(error) }
}
task.resume()
After parsing your json, you can sort your score array like this
var scoreArray = ["500", "0", "2000", "300", "900"]
array.sort { $0.compare($1, options: .numeric) == .orderedAscending }
I did something like this before
First I created two arrays of dictionary
var jsonArray = [(name:String, score:String)]()
var sortedscoreArray:[(name: String, score: String)] = []
and in getting json data you can create for loop
for I in 0..< jsonData.count{
Let jsonInfo = jsonData[i]
jsonArray.append((name: jsonInfo[“Name”].string!, score: jsonInfo[“Score"].string!))
}
and after you fill the json array pass it to sortedArray
sortedscoreArray = jsonArray.sorted(by: { $0.score < $1.score })
If array contains dictionary then you can use this code for sorting:
let sortedArray = json.sort { $0["Score"] as! Int < $1["Score"] as! Int }
print(sortedArray)
and if you are using bean class then you can use dot(.) properties for sorting:
let sortedArray = json.sort { $0.Score < $1.Score }
print(sortedArray)
let sortedResults = self.json?.sorted(by: {$0.name ?? EMPTY_STRING < $1.name ?? EMPTY_STRING }) ?? []
Are there anything look like java reflection in swift or I have to always map one by one attribute like following code?
class User: Model {
var name: String
override init(data: Dictionary<String, AnyObject>){
super.init(data: data)
self.name = data["name"] as? String
if let vouchers_count = data["vouchers_count"] as? Int {
self.vouchers_count = vouchers_count
}
}
You could use libraries such as EVReflection.
import EVReflection
class User: EVObject {
var name: String = ""
var vouchers_count: Int = 0
}
let alice = User(json: "{\"name\":\"alice\",\"vouchers_count\":1}")
debugPrint(alice)
/*
testUser = {
"name" : "alice",
"vouchers_count" : 1
}
*/
let bob = User(json: "{\"name\":\"bob\"}")
debugPrint(bob)
/*
testUser = {
"name" : "bob",
"vouchers_count" : 0
}
*/
Swift 3 currently only has fairly basic reflection capability using Mirror - as documented here.
In your case you would use it as:
let user = User()
user.name = "Michael"
let mirror = Mirror(reflecting: user)
print(mirror)
for case let (label?, value) in mirror.children {
print ("\(label) \(subjectType) = \(value))
// ... Prints name String = Michael
}
A good article can be found here.