Objectmapper get array of one item within JSON - arrays

So I have the following JSON, which I am using together with ObjectMapper and Realm.
{
"result": [
{
"id": 20,
"types": [
"now"
],
"url": "/nl/whereto/ezrhgerigerg",
"categories": [
{
"id": 39,
"name": "Food "
},
{
"id": 21,
"name": "Varia"
}
]
},
My problem is getting the data from "types", which for some items in the array says "now" or "later", and is empty for other items (hence, no types item is given).
I tried to do the following in my mapping:
class Publication: Object, Mappable {
dynamic var id:Int = 0
var typez = List<getType>()
dynamic var url:String?
required convenience init?(_ map: Map) {
self.init()
}
override static func primaryKey() -> String? {
return "id"
}
func mapping(map: Map) {
id <- map["id"]
typez <- map["types"]
url <- map["url"]
}
}
class getType: Object, Mappable {
dynamic var text: String = ""
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
text <- map[""]
}
}
When I check the Realm database, you can see that typez, an array of [getType] was made, but it's empty for all items (even the ones where types is "now"). The other two items (id and url) are filled in in the database.
What am I doing wrong that it won't save to the database?

Because Realm cannot detect assigning List properties since List property is not Objective-C type. So List properties should be declared as let, and should not be nil. You should use append/remove.../insert...method to modifying theList`.
So your code
typez <- map["types"]
doesn't work, since you assign values to the typez property directly.
The workaround is like the following:
func mapping(map: Map) {
...
var typez: [String]? = nil
typez <- map["types"]
typez?.forEach { t in
let obj = getType()
obj.text = t
self.typez.append(obj)
}
...
First, store the mapped value to the local variable (it is string array). Then convert the string array to objects. Then append the objects to the List property.

Related

Swiftui User Multiple Selection To Array of Object

I have the following response from API
"features": [
{
"name": "Safety",
"_id": "636a638959d10a2603b8d645",
"values": [
Array of String
]
},
{
"name": "Entertainment",
"_id": "636a64312bbe0cd292a1ffc6",
"values": [
Array of String
]
Which I decode it with :
struct Feature : Codable , Hashable{
var name : String = ""
var values : [Value] = []
}
struct Value : Codable, Hashable{
var value : String = ""
var unit : String = ""
}
And in the view is render it like :
var body: some View {
VStack{
HStack{
Text("Choose Your Features").font(Font.body.bold())
Spacer()
}.padding(.leading, 15)
ScrollView(.vertical, showsIndicators: false){
VStack{
ForEach(Array(features.enumerated()), id: \.offset) { featureIndex, feature in
HStack{
Text(feature.name).font(Font.body.bold())
Spacer()
}.padding(.bottom , 10)
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing : 10){
ForEach(Array(feature.values.enumerated()), id: \.offset) { valueIndex, value in
FeatureCell(isSelected: $isSelected, value: value).onTapGesture{
// here
}
}
}
Divider().padding(10)
}
}.padding(15)
}
}
}
}
The user may select multiple item from each feature values list, Now Im really confused about how to store these selections in an array of features object again, I tried almost every thing like Array, Set and Dictionaries but could not reach any solution.
Update : This is the json object I should send back
{
"features": [
{
"Safety": [
"value1",
"value9",
"value3"
]
},
{
"Entertainment": [
"value7",
"value2",
"value8"
]
}
]
}
Any help or ideas will be much appreciated
You usually want to use a Set to store which items are selected. This set should be a State variable instantiated in the parent view. The onTapGesture closure will add or remove the value to the set. If the FeatureCell needs to know whether the value is selected, simply use the .contains method.
struct FeatureValueSelectionView: View {
// The feature whose values we are displaying
let feature: Feature
// Note: You may have to manually conform Value to the Hashable protocol
#State private var selectedValues = Set<Value>()
var body: some View {
ForEach(feature.values) { value in
FeatureCell(selected: selectedValues.contains(value), value: value)
.onTapGesture { selectedValues.toggle(value) }
}
}
}
For toggling a value in a set, I like to use this simple extension:
extension Set {
public mutating func toggle(_ element: Element) {
if self.contains(element) {
self.subtract([element])
} else {
self.insert(element)
}
}
}

Firebase replacing an entire array returning "Unsupported type: __SwiftValue"

I have a document that contains an array with the following structure:
"songs": [
{ "id": "asdasdasd", "votes": 1, "downVotedBy": [], "upVotedBy": ["user1"] }
]
When I'm trying to upvote the song using other user, after adding the user to the "upVotedBy" array, adding one to "votes" and ordering the songs based on the votes, I want to replace the entire array, so I do something like this:
ref.updateData([ "songs": songs ])
Where songs is an array containing elements of this custom class:
public struct SongModel: Codable {
var id: String
var votes: Int = 1
var upVotedBy: [String]
var downVotedBy: [String]
enum CodingKeys: String, CodingKey {
case id
case votes
case upVotedBy
case downVotedBy
}
}
But I get an Exception "Unsupported type: __SwiftValue"
My temporary solution was to convert the array of SongModel to an array of dictionaries:
private func convertSongToDic(songs: [SongModel]) -> [Any]{
var newSongs = [Any]()
for song in songs {
newSongs.append([
"id": song.id,
"votes": song.votes,
"upVotedBy": song.upVotedBy,
"downVotedBy": song.downVotedBy
])
}
return newSongs
}

How to display parsed JSON data in UI?

I have parsed data from a JSON object that I received from an API call. As of now, I can print the JSON data in the debugger console, however I am trying to convert the parsed JSON back into data that can be displayed in the UI. I have two models, and an example of a JSON object looks as such:
{
"query": "milk",
"sort": "relevance",
"responseGroup": "base",
"totalResults": 693,
"start": 1,
"numItems": 10,
"items": [{
"itemId": 10291863,
"parentItemId": 10291863,
"name": "Carnation Vitamin D Added Evaporated Milk, 12 oz",
"msrp": 1.79,
"salePrice": 1.48
}]
}
I only want to display information that details the name and salePrice keys. However since the JSON is nested I don't know how to reach to that layer in order to retrieve the values. Here is my data model code:
struct Item: Codable {
let query: String
let sort: String
let responseGroup: String
let totalResults: Int
let start: Int
let numItems = 25
let items: [Product]
}
struct Product: Codable {
let name: String
let salePrice: Double
}
Code to my ViewController:
class ViewController: UIViewController {
#IBOutlet weak var itemField: UITextField!
#IBAction func filterButton(_ sender: Any) {
var components = URLComponents()
components.scheme = "http"
components.host = "api.walmartlabs.com"
components.path = "/v1/search"
let queryItemKey = URLQueryItem(name: "apiKey", value: secretKey)
var queryItemQuery = URLQueryItem(name: "query", value: itemField.text)
components.queryItems = [queryItemKey, queryItemQuery]
let searchURL = components.url
//Task to make API Network Call
guard let url = components.url else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Item object
let productData = try JSONDecoder().decode(Item.self, from: data)
print(productData)
} catch let jsonError {
print(jsonError)
}
}.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The print log shows the following when I run my code (again, I only want to show the name and salePrice values. Is there a way I can place these values in an array or convert these values in a way I can populate my UI with? Thanks in advance.
Create a data source array
var products = [Product]()
After parsing the data assign the products array to the data source array and reload the table view
...
let productData = try JSONDecoder().decode(Item.self, from: data)
self.products = productData.items
DispatchQueue.main.async {
self.tableView.reloadData()
}
In numberOfRowsInSection return products.count
In cellForRow get the current product and assign the values to labels
let product = products[indexPath.row]
cell.textLabel!.text = product.name
cell.detailTextLabel!.text = "\(product.salePrice)"
You can try a simple loop:
for product in productData.items {
print("\(product.name) : \(product.salePrice)")
}
or in a closure way:
productData.items.forEach { print("\($0.name) : \($0.salePrice)") }
In fact your products are already in an array: productData.items
EDIT after comments
You can map your fields to get each values in separate arrays:
let names = productData.items.map { $0.name }
let salePrices = productData.items.map { $0.salePrice }

Filter a Parse query with an Array of [AnyObject] in Swift

I have an array of AnyObject objects in Swift (eventually, this array will be populated by querying a Parse database). Each object in the array has attributes of a publication, such as fullTitle, url, and journal. How can I filter the array to select all objects that match the search string for any value (e.g. where the fullTitle, url, or journal include "Forbes")?
Below is example code from a playground. First the sample array:
var publication1 = [
"fullTitle": "My first blog",
"url": "www.forbes.com/post1",
"journal": "Forbes
]
var publication2 = [
"fullTitle": "My second blog",
"url": "www.wsj.com/post1",
"journal": "Wall Street Journal"
]
var publications: [AnyObject] = [publication1, publication2]
Then, the filter function:
func filterContentForSearchText(searchText: String) {
let filteredPublications = publications.filter() {
if let fullTitle = ($0)["fullTitle"] as? String {
return fullTitle.rangeOfString(searchText) != nil
} else {
return false
}
}
}
Now, if I call the function with "first" as an argument, it should return the first object out of the array:
println(filterContentForSearchText("first"))
However, this command gives no result. How can I fix this? Also, how can I query all fields of the object for the searchText, not just the fullTitle field?
Thank you.
Here's a simple example that returns an array of matches in all fields:
func filterContentForSearchTextInAllFields(searchText: String) -> [String] {
var results = [String]()
for publication in publications {
for (key, value) in publication {
if (value as NSString).containsString(searchText) {
results.append(value)
}
}
}
return results
}
println(filterContentForSearchTextInAllFields("blog"))
This one only works on titles:
func filterContentForSearchText(searchText: String) -> [String] {
var results = [String]()
for publication in publications {
if let fullTitle = publication["fullTitle"] {
if (fullTitle as NSString).containsString(searchText) {
results.append(fullTitle)
}
}
}
return results
}
println(filterContentForSearchText("first"))
UPDATE
Here's a version for what you've asked in the comments:
func filterContentForSearchText(searchText: String) -> [[String:String]] {
var results = [[String:String]]()
for publication in publications {
if let fullTitle = publication["fullTitle"] as? String {
if (fullTitle as NSString).containsString(searchText) {
results.append(publication as! [String : String])
}
}
}
return results
}
println(filterContentForSearchText("first"))
Your "rows" are dictionaries: in the loop we assign each one to the "publication" variable, so we just take the one whose title matches the search terms then append it to an array of dictionaries.
Here is the route I went. Instead of an array of AnyObject I just let Swift infer the type
var publications = [publication1, publication2]
func searchPublications(seachText: String) -> [[String: String]] {
let filteredPublications = publications.filter { $0
for (_, value) in $0 {
if let found = value.rangeOfString(seachText, options: NSStringCompareOptions.CaseInsensitiveSearch, range: Range<String.Index>(start: value.startIndex, end: value.endIndex), locale: NSLocale.currentLocale()) {
return true
}
}
return false
}
return filteredPublications
}
Swift 3.0
This is worked for me.
I want filter data by it's status 0 or 1.
var arrRooms:[[String:AnyObject]] = [
[
"id": 30 as AnyObject,
"name": "Earth" as AnyObject,
"status": 0 as AnyObject,
"duration": 10 as AnyObject
],
[
"id": 27 as AnyObject,
"name": "Mars" as AnyObject,
"status": 1 as AnyObject,
"duration": 0 as AnyObject
]
]
let StatusPredicate = NSPredicate(format: "status = 1 ")
let arrFilter:[[String:AnyObject]] = (arrRooms as NSArray).filtered(using: StatusPredicate) as! [[String : AnyObject]]
print(arrFilter);
// output [["name": Mars, "status": 1, "id": 27, "duration": 0]]
it may be useful.thanks

How to save an array to a Realm Object

I am new to using Realm. Is there an easy way to save an array to a realm object? I am receiving my data from a JSON REST call:
class SomeClass: RLMObject {
dynamic var id = 0
dynamic var name = ""
dynamic var array: NSArray
func checkForUpdates() {
// Download JSON data here... The results have an array inside of them.
SomeClass.createOrUpdateInDefaultRealmWithObject(SomeNSDictionary)
}
override class func primaryKey() -> String! {
return "id"
}
}
Is it possible to save the array in the JSON results in Realm?
Thanks.
Realm has a special RLMArray type, which allows storing a collection of RLMObject's tied to a parent RLMObject. For example, say you had the following JSON:
{
"name": "John Doe",
"aliases": [
{"alias": "John"},
{"alias": "JD"}
]
}
You could model this with the following classes:
class Alias: RLMObject {
dynamic var alias = ""
}
class Person: RLMObject {
dynamic var name = ""
dynamic var aliases = RLMArray(objectClassName: "Alias")
}
So you could simply create a Person object with the following API call:
Person.createInRealm(realm, withObject: jsonObject)
You can learn more about how RLMArrays work from Realm's reference documentation: http://realm.io/docs/cocoa/0.80.0/api/Classes/RLMArray.html

Resources