I'm trying to filter the data from the sub_category array and show on the data in which isSelected is true. I have tried using flatmap and filter but am still not able to achieve the desired output. Please help.
{
"data": [
{
"category_name": "Social Sites",
"sub_category": [
{
"sub_category_name": "Instagram",
"isSelected" : true
},
{
"sub_category_name": "Facebook",
"isSelected" : true
},
{
"sub_category_name": "Twitter",
"isSelected" : false
}
]
},
{
"category_name": "",
"sub_category": [
{
"sub_category_name": "State",
"isSelected" : false
}
]
},
{
"category_name": "Sports",
"sub_category": [
{
"sub_category_name": "Cricket",
"isSelected" : true
},
{
"sub_category_name": "Hockey",
"isSelected" : false
}
]
}
]
}
Desired Output is only for isSelected = true
{
"data": [
{
"category_name": "Social Sites",
"sub_category": [
{
"sub_category_name": "Instagram",
"isSelected" : true
},
{
"sub_category_name": "Facebook",
"isSelected" : true
}
]
},
{
"category_name": "Sports",
"sub_category": [
{
"sub_category_name": "Cricket",
"isSelected" : true
}
]
}
]
}
I have tried using flatMap and filter, the following is my code.(But not able to achieve the desired output.)
let filtered = self.itemModelArray.flatMap { item in
item.subCategory!.filter({ (subCat) -> Bool in
subCat.isSelected == true
})
}
The above code gives me the array of subCategory,that is not what I want.
The following is my model
// MARK: - Item
struct Item: Codable {
let category_name: String
var sub_category: [SubCategory]
init(cat:String,subCat:[SubCategory]){
self.category_name = cat
self.sub_category = subCat
}
}
// MARK: - SubCategory
struct SubCategory: Codable {
let sub_category_name: String
var isSelected : Bool = false
init(subCat:String){
self.sub_category_name = subCat
}
Something like this would filter out both empty categories & non-selected sub-categories:
import Foundation
let json: String = <the json in the question, omited for brevity>
struct Model: Codable {
let data: [Category]
struct Category: Codable {
let category_name: String
let sub_category: [SubCategory]
}
struct SubCategory: Codable {
let sub_category_name: String
let isSelected: Bool
}
}
// Note: Handle any errors properly in production code
let model = try? JSONDecoder().decode(Model.self, from: Data(json.utf8))
var output: [Model.Category] = []
for category in model?.data ?? [] {
let selectedSubCategories = category.sub_category.filter(\.isSelected)
if !selectedSubCategories.isEmpty {
output.append(Model.Category(category_name: category.category_name, sub_category: selectedSubCategories))
}
}
which produces an array of 2 categories:
dump(output)
▿ 2 elements
▿ main.Model.Category
- category_name: "Social Sites"
▿ sub_category: 2 elements
▿ main.Model.SubCategory
- sub_category_name: "Instagram"
- isSelected: true
▿ main.Model.SubCategory
- sub_category_name: "Facebook"
- isSelected: true
▿ main.Model.Category
- category_name: "Sports"
▿ sub_category: 1 element
▿ main.Model.SubCategory
- sub_category_name: "Cricket"
- isSelected: true
Related
I am trying to consume data from an api in swift, json data has successfully been delivered back to the app but the json response from my backend is very complex hence forming struct for my model is very difficult. I'm able to only retrieve the simple strings but if I add the objects and arrays everything stops working
[
{
"type": "movie",
"id": "ffff-ddd968-4cf0-a939-8skeu",
"title": "Sorority Row",
"description": "When five laldkdk",
"categories": [],
"genres": [
"Mystery",
"Horror"
],
"links": {
"amazonPrime": [
{
"link": "somelink",
"format": "native_ios"
},
{
"link": "somelink",
"format": "native_ios"
}
],
"appleTvPlus": [],
"disneyPlus": []
"iTunes": [
{
"link": "www.somelink",
"format": "webview_computer"
}
],
"netflix": [],
"youTubePremium": []
},
"promoted": false,
"certification": "18",
"releaseDate": "2009-09-09",
"runTime": 101,
"userRating": null,
"inUserList": false,
"packShot": {
"thumbnail": "imageurl"
},
"backdrop": {
"thumbnail": "imageurl"
}
}
]
struct Responder: Codable {
let type: String
let id: String
let description: String
let title: String
let promoted: Bool
let certification: String
let firstAirDate: String
let lastAirDate: String
let numberEpisodes: Int
let numberSeasons: Int
let userRating: Int?
let inUserList: Bool
let thumbnail: PackShotObj
let amazonPrime: linksObj
}
struct PackShotObj: Codable {
let packShot: [String]
}
struct linksObj: Codable {
let link: String
let format: String
}
struct genres: Codable {
let empty: String
}
Here is the code that works, decoding your json data. Note the differences between my struct models and yours. You will need to consult the docs of the server to determine which fields are optionals and adjust the code for that:
struct ContentView: View {
#State var responders: [Responder] = []
var body: some View {
List(responders) { responder in
Text(responder.title)
Text(responder.description)
Text(responder.releaseDate)
}
.onAppear {
let json = """
[
{
"type": "movie",
"id": "ffff-ddd968-4cf0-a939-8skeu",
"title": "Sorority Row",
"description": "When five laldkdk",
"categories": [],
"genres": [
"Mystery",
"Horror"
],
"links": {
"amazonPrime": [
{
"link": "somelink",
"format": "native_ios"
},
{
"link": "somelink",
"format": "native_ios"
}
],
"appleTvPlus": [],
"disneyPlus": [],
"iTunes": [
{
"link": "www.somelink",
"format": "webview_computer"
}
],
"netflix": [],
"youTubePremium": []
},
"promoted": false,
"certification": "18",
"releaseDate": "2009-09-09",
"runTime": 101,
"userRating": null,
"inUserList": false,
"packShot": {
"thumbnail": "imageurl"
},
"backdrop": {
"thumbnail": "imageurl"
}
}
]
"""
// simulated API data
let data = json.data(using: .utf8)!
do {
self.responders = try JSONDecoder().decode([Responder].self, from: data)
print("\n---> responders: \n \(responders)\n")
} catch {
print("\n---> decoding error: \n \(error)\n")
}
}
}
}
// MARK: - Responder
struct Responder: Identifiable, Codable {
let type, id, title, description: String
let categories: [String]
let genres: [String]
let links: Links
let promoted: Bool
let certification, releaseDate: String
let runTime: Int
let userRating: Int?
let inUserList: Bool
let packShot, backdrop: Backdrop
}
// MARK: - Backdrop
struct Backdrop: Codable {
let thumbnail: String
}
// MARK: - Links
struct Links: Codable {
let amazonPrime: [Provider]
let appleTvPlus: [Provider]
let disneyPlus: [Provider]
let iTunes: [Provider]
let netflix: [Provider]
let youTubePremium: [Provider]
}
struct Provider: Codable {
let link, format: String
}
Just copy and paste this model to file and you are good to go.
struct Responder: Codable {
let type, id, title, welcomeDescription: String
let categories: [String]
let genres: [String]
let links: Links
let promoted: Bool
let certification, releaseDate: String
let runTime: Int
let userRating: Any?
let inUserList: Bool
let packShot, backdrop: Backdrop
enum CodingKeys: String, CodingKey {
case type, id, title
case welcomeDescription = "description"
case categories, genres, links, promoted, certification, releaseDate, runTime, userRating, inUserList, packShot, backdrop
}
}
// MARK: - Backdrop
struct Backdrop: Codable {
let thumbnail: String
}
// MARK: - Links
struct Links: Codable {
let amazonPrime: [AmazonPrime]
let appleTvPlus, disneyPlus: [String]
let iTunes: [AmazonPrime]
let netflix, youTubePremium: [String]
}
// MARK: - AmazonPrime
struct AmazonPrime: Codable {
let link, format: String
}
I want to compare the two models mainArray and resultArray. If category_name and sub_category_name are found to be same, the mainArray is_selected is to be changed to true. How can I achieve this? They both are models.
var mainArray : [Item] = [
{
"category_name": "Wellness",
"sub_category": [
{
"sub_category_name": "Health",
"is_selected": false
},
{
"sub_category_name": "Psychedelics",
"is_selected": false
},
{
"sub_category_name": "Meditation",
"is_selected": false
},
{
"sub_category_name": "Nutrition",
"is_selected": false
},
{
"sub_category_name": "Outdoors",
"is_selected": false
},
{
"sub_category_name": "Medicine",
"is_selected": false
},
{
"sub_category_name": "Mindfulness",
"is_selected": false
},
{
"sub_category_name": "Fitness",
"is_selected": false
},
{
"sub_category_name": "Weights",
"is_selected": false
},
{
"sub_category_name": "Veganism",
"is_selected": false
}
]
},
{
"category_name": "Hustle",
"sub_category": [
{
"sub_category_name": "Tik Tok",
"is_selected": false
},
{
"sub_category_name": "ClubHouse",
"is_selected": false
},
{
"sub_category_name": "Stocks",
"is_selected": false
},
{
"sub_category_name": "Networking",
"is_selected": false
},
{
"sub_category_name": "Small business",
"is_selected": false
},
{
"sub_category_name": "Instagram",
"is_selected": false
},
{
"sub_category_name": "Enterpreneurship",
"is_selected": false
},
{
"sub_category_name": "Real Estate",
"is_selected": false
},
{
"sub_category_name": "Pitch practice",
"is_selected": false
}
]
}
]
var resultArray : [Item] = [
{
"category_name": "Wellness",
"sub_category": [
{
"sub_category_name": "Psychedelics"
},
{
"sub_category_name": "Fitness"
}
]
},
{
"category_name": "Hustle",
"sub_category": [
{
"sub_category_name": "ClubHouse"
}
]
}
]
This is the Item Model
// MARK: - Item
class Item: Codable {
let category_name: String
let sub_category: [SubCategory]
enum CodingKeys: String, CodingKey {
case category_name = "category_name"
case sub_category = "sub_category"
}
init(categoryName: String, subCategory: [SubCategory]) {
self.category_name = categoryName
self.sub_category = subCategory
}
}
First of all
var mainArray : [Item] = [{
makes no sense because the data is a JSON string rather than an array of Item.
Second of all it's a bit tricky to update an array in place because you might have to deal with value semantics.
This suggestion works even with structs. I changed the names to more Swift-compliant names
let main = """
[
{
"category_name": "Wellness",
"sub_category": [ ...
let result = """
[
{
"category_name": "Wellness",
"sub_category": [ ...
struct Category: Codable {
let name: String
var subCategories: [SubCategory]
private enum CodingKeys: String, CodingKey {
case name = "category_name"
case subCategories = "sub_category"
}
}
struct SubCategory: Codable {
let name: String
var isSelected = false
private enum CodingKeys: String, CodingKey { case name = "sub_category_name" }
}
func select(main: inout [Category], from result: [Category]) {
result.forEach { category in
guard let mainCategoryIndex = main.firstIndex(where: {$0.name == category.name}) else { return }
for subCategory in category.subCategories {
guard let subCategoryIndex = main[mainCategoryIndex].subCategories.firstIndex(where: {$0.name == subCategory.name}) else { return }
main[mainCategoryIndex].subCategories[subCategoryIndex].isSelected = true
}
}
}
do {
var main = try JSONDecoder().decode([Category].self, from: Data(main.utf8))
let result = try JSONDecoder().decode([Category].self, from: Data(result.utf8))
select(main: &main, from: result)
print(main)
} catch {
print(error)
}
Here i provide the code worked sample as per the guideline given below and seems to get null values.
Here is my complete JSON Data,
some: {
"success": true,
"data":
[
{
"15-10-2020": [
{
"id": 100,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 101,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 102,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
}
],
"30-09-2020": [
{
"snf_id": 301,
"details": {
"_id": 8,
"_title": "My Title"
},
"created_at": "2020-09-30"
}
]
}
],
"message": "Successfully Retrieved"
}
struct Response : Codable {
var success : Bool?
var data : [Data]?
var message : String?
}
struct Data: Codable {
var snf_id: Int?
var details: Details?
var created_at: String?
}
// MARK: - Details
struct Details: Codable {
var _id: Int?
var _title: String?
}
let Response = try JSONDecoder().decode(Response.self, from: data)
Returns null value for data,
▿ Response
▿ success : Optional
- some : true
▿ data : Optional<Array>
▿ some : 1 element
▿ 0 : Data
- snf_id : nil
- details : nil
- created_at : nil
▿ message : Optional
- some : "Successfully Retrieved"
There is no myData key in your json , your json top structure is an array that contains elements where every element value is an array like [[String: [Root]]]
struct Root: Codable {
let id: Int?
let details: Details
let createdAt: String
let snfID: Int?
enum CodingKeys: String, CodingKey {
case id, details
case createdAt = "created_at"
case snfID = "snf_id"
}
}
// MARK: - Details
struct Details: Codable {
let id: Int
let title: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case title = "_title"
}
}
And to decode
let res = try JSONDecoder().decode([[String: [Root]]].self,from:data)
some: {
"success": true,
"data":
[
{
"15-10-2020": [
{
"id": 100,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 101,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 102,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
}
],
"30-09-2020": [
{
"snf_id": 301,
"details": {
"_id": 8,
"_title": "My Title"
},
"created_at": "2020-09-30"
}
]
}
],
"message": "Successfully Retrieved"
}
struct Response : Codable {
var success : Bool?
**var data : [[String:[Data]]]?**
var message : String?
}
struct Data: Codable {
var snf_id: Int?
var details: Details?
var created_at: String?
}
// MARK: - Details
struct Details: Codable {
var _id: Int?
var _title: String?
}
let Response = try JSONDecoder().decode(Response.self, from: data)
I am creating a database App in Swift3, where I have to display the data in UITableView from JSON. Below is my JSON :
{
"Success": 1,
"data": [{
"Session_Details": [{
"Start_Time": "08:00",
"End_Time": "10:00",
"Tag_Details": [{
"Tag_Id": 1,
"Tag_Name": "Test 1",
"Tag_Order": 4
}]
},
{
"Start_Time": "10:30",
"End_Time": "12:30",
"Tag_Details": [{
"Tag_Id": 2,
"Tag_Name": "Test 2",
"Tag_Order": 1
}]
},
{
"Start_Time": "10:30",
"End_Time": "12:30",
"Tag_Details": [{
"Tag_Id": 3,
"Tag_Name": "Test 3",
"Tag_Order": 3
}]
},
{
"Start_Time": "13:30",
"End_Time": "15:20",
"Tag_Details": [{
"Tag_Id": 1,
"Tag_Name": "Test 1",
"Tag_Order": 4
}]
}
]
}]
}
I have already parsed the JSON and getting all the JSON data.
My problem is I have to create an array of 'Tag_Details' that should have unique value, It means Tag_Id should be unique. Also I have to set the array in ascending order based on Tag_Order key.
I am trying below code but not working :
var sessions : [SessionData]! {
return AgendaDataManager.sharedInstance.sessionData
}
let sortedResults = session.tagDetails!.sortedArray(using: [NSSortDescriptor(key: "tagOrder", ascending: true)])
let sessionTag = ((session.tagDetails as AnyObject).allObjects as! [TagData])[0]
Please suggest me. Thank you.
If you are coding in Swift 3 and can't work with Codable protocol
First you should structure your json data. You can use this helper quick type that will give a pretty good start point:
struct Root {
let success: Bool
let data: [Datum]
}
struct Datum {
let sessionDetails: [SessionDetail]
}
struct SessionDetail {
let startTime: String
let endTime: String
let tagDetails: [TagDetail]
}
struct TagDetail {
let tagId: Int
let tagName: String
let tagOrder: Int
}
Them you would need to create a custom initialiser for your root structure that takes a Data parameter (JSON Data):
typealias Dictionary = [String: Any]
typealias Dictionaries = [[String: Any]]
extension Root {
init?(_ data: Data) {
let dictionary = (try? JSONSerialization.jsonObject(with: data)) as? Dictionary ?? [:]
success = dictionary["Success"] as? Bool == true
guard success else {
return nil
}
self.data = (dictionary["data"] as! Dictionaries).map(Datum.init)
}
}
And initialisers that takes a dictionary for all structures.
extension Datum {
init(dictionary: [String: Any]) {
sessionDetails = (dictionary["Session_Details"] as! Dictionaries)
.map(SessionDetail.init)
}
}
extension SessionDetail {
init(dictionary: [String: Any]) {
startTime = dictionary["Start_Time"] as! String
endTime = dictionary["End_Time"] as! String
tagDetails = (dictionary["Tag_Details"] as! Dictionaries).map(TagDetail.init)
}
}
extension TagDetail: CustomStringConvertible {
init(dictionary: [String: Any]) {
tagId = dictionary["Tag_Id"] as! Int
tagName = dictionary["Tag_Name"] as! String
tagOrder = dictionary["Tag_Order"] as! Int
}
var description: String {
return "TagDetail(Id: \(tagId) - Name: \(tagName) - Order: \(tagOrder))"
}
}
Next you will need to make TagDetail conform to Equatable and Comparable:
extension TagDetail: Equatable, Comparable {
static func == (lhs: TagDetail, rhs: TagDetail) -> Bool {
return lhs.tagId == rhs.tagId
}
static func < (lhs: TagDetail, rhs: TagDetail) -> Bool {
return lhs.tagOrder < rhs.tagOrder
}
}
Once you accomplish all these steps you can easily filter and sort your objects:
let data = Data("""
{
"Success": 1,
"data": [{
"Session_Details": [{
"Start_Time": "08:00",
"End_Time": "10:00",
"Tag_Details": [{
"Tag_Id": 1,
"Tag_Name": "Test 1",
"Tag_Order": 4
}]
},
{
"Start_Time": "10:30",
"End_Time": "12:30",
"Tag_Details": [{
"Tag_Id": 2,
"Tag_Name": "Test 2",
"Tag_Order": 1
}]
},
{
"Start_Time": "10:30",
"End_Time": "12:30",
"Tag_Details": [{
"Tag_Id": 3,
"Tag_Name": "Test 3",
"Tag_Order": 3
}]
},
{
"Start_Time": "13:30",
"End_Time": "15:20",
"Tag_Details": [{
"Tag_Id": 1,
"Tag_Name": "Test 1",
"Tag_Order": 4
}]
}
]
}]
}
""".utf8)
if let root = Root(data), root.success,
let sessionDetails = root.data.first?.sessionDetails {
for detail in sessionDetails {
print(detail)
}
let allTagDetails = sessionDetails.flatMap{$0.tagDetails}
let tagDetailsSorted = allTagDetails.sorted()
print("\n\n\n")
var set = Set<Int>()
let tagDetailsSortedSet = tagDetailsSorted.filter({ set.insert($0.tagId).inserted })
tagDetailsSortedSet.map{print($0)}
}
This will print
SessionDetail(startTime: "08:00", endTime: "10:00", tagDetails:
[TagDetail(Id: 1 - Name: Test 1 - Order: 4)])
SessionDetail(startTime:
"10:30", endTime: "12:30", tagDetails: [TagDetail(Id: 2 - Name: Test 2
- Order: 1)])
SessionDetail(startTime: "10:30", endTime: "12:30", tagDetails: [TagDetail(Id: 3 - Name: Test 3 - Order: 3)])
SessionDetail(startTime: "13:30", endTime: "15:20", tagDetails:
[TagDetail(Id: 1 - Name: Test 1 - Order: 4)])
and
TagDetail(Id: 2 - Name: Test 2 - Order: 1)
TagDetail(Id: 3 - Name: Test 3 - Order: 3)
TagDetail(Id: 1 - Name: Test 1 - Order: 4)
I have a JSON response that I store as an NSMutableDictionary that looks like this:
{
"list": { "ID1", "ID2", "ID3" },
"items": {
"ID1" : { "name" : "shoe" },
"ID2" : { "name" : "pants" },
"ID3" : { "name" : "hat" }
}
}
i need to have the NSMutableDictionary add entries from any additional JSON responses, so if i receive a new response as follows:
{
"list": { "ID4", "ID5", "ID6" },
"items": {
"ID4" : { "name" : "shirt" },
"ID5" : { "name" : "tie" },
"ID6" : { "name" : "glasses" }
}
}
the updated NSMutableDictionary needs to appear as follows:
{
"list": { "ID1", "ID2", "ID3", "ID4", "ID5", "ID6" },
"items": {
"ID1" : { "name" : "shoe" },
"ID2" : { "name" : "pants" },
"ID3" : { "name" : "hat" },
"ID4" : { "name" : "shirt" },
"ID5" : { "name" : "tie" },
"ID6" : { "name" : "glasses" }
}
}
Unfortunately, when i call addEntriesFromDictionary with the additions, i get this:
{
"list": { "ID1", "ID2", "ID3" },
"items": {
"ID1" : { "name" : "shoe" },
"ID2" : { "name" : "pants" },
"ID3" : { "name" : "hat" }
}
}
"list": { "ID4", "ID5", "ID6" },
"items": {
"ID4" : { "name" : "shirt" },
"ID5" : { "name" : "tie" },
"ID6" : { "name" : "glasses" }
}
}
Assuming we have the same dictionaries as in your example:
let key1 = "list"
let key2 = "items"
var rec = [key1:["ID1","ID2","ID3"],
key2:["ID1":["name":"shoe"],"ID2":["name":"pants"],"ID3":["name":"hat"]]] as [String : Any]
let inc = [key1:["ID4","ID5","ID6"],
key2:["ID4":["name":"shirt"],"ID5":["name":"tie"],"ID6":["name":"glasses"]]] as [String : Any]
I used the following rationale to find a solution:
... implemented this code snippet hereafter:
func merge(_ inc:[String:Any], into rec: inout [String:Any]) -> [String:Any] {
for (_, vals) in inc {
if var recKeys = rec[key1] as? [String],
var recItems = rec[key2] as? [String:[String:String]],
let incItems = inc[key2] as? [String:[String:String]] {
if let incValIds = vals as? [String] {
for id in incValIds {
if let newVal = incItems[id] {
if recKeys.contains(id) {
for (newValId, newValObj) in newVal {
guard var tab = recItems[id] else { continue }
tab[newValId] = newValObj
recItems[id] = tab
}
} else {
recKeys.append(id)
recItems[id] = newVal
}
}
}
}
rec[key1] = recKeys
rec[key2] = recItems
}
}
return rec
}
... and used this function as described hereunder to get the result defined below:
let updatedInfo = merge(inc, into: &rec)
print(updatedInfo)
You can now properly merge the two provided dictionaries as desired.