I wanna collect all cities with towns in a dictionary like [String: [String]]. But because of models, I cannot append townNames to dictionary. My models like below. Because of the [TownModel], I cannot get townName as string. It works with [String: [TownModel]] dictionary. But I want to use my [String: [String]] dictionary. What should I add to my code ?
open class CityListResponseModel: BaseResponseProtocol {
public var cityCode: Int?
public var cityName: String?
public var towns: [TownModel]?
}
open class TownModel: Codable {
public var townCode: Int?
public var townName: String?
}
//My code
var dictionary: [String: [String]]
let dict = list.reduce(into: [:]) { $0[$1.cityName, default: []].append($1.towns) }
First, improve your model. There is no reason to have an optional array, or a non-final class.
(There may also be no reason for cityCode or cityName to be optional either. The "city" prefix also seems redundant, as does the "town" one. Check on all that.)
final class CityListResponseModel: BaseResponseProtocol {
public var cityCode: Int?
public var cityName: String?
public var towns: [TownModel]
}
With that,
Dictionary(
list.compactMap { city in
city.cityName.map { ($0, city.towns.compactMap(\.townName)) }
},
uniquingKeysWith: +
)
You can use like:
var dictionary: [String: [String]] = [:]
let list: [CityListResponseModel] = []
list.forEach { (city) in
dictionary[city.cityName ?? ""] = city.towns?.map({$0.townName ?? ""})
}
Related
This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 13 hours.
Dipesh Pokhrel wants to draw more attention to this question:
I have gone throught the documentation no where its specifically mentioned. how to deal with the multidimensional string objects
I have a realm class which contains the a multi dimensional string, realm in Decodable is throwing an error to while parsing, created class to support realm.
class Categories : Object,Decodable {
// var assetSum : [[String]]? // In swift originally
let assetSum = RealmSwift.List<String>() // modified to support list
#objc var id : String?
#objc var dn : String?
How to fix this , to be more Generalise how to store var assetSum : [[String]]? this kind of value in realm?
I have gone through the documentation of realm but could not find something related to this
Realm supports basic types like Int, String, Date etc. and several collections types like List (Array), Map (Dictionary) from the box. For the other your custom types you can use json serialization which works pretty quick.
It can be implemented with two variables where persistent private one is for storing data and public one is for accessing e.g:
import RealmSwift
class Categories: Object {
#Persisted private var assetSum: Data?
var assetSumValue: [[String]]? {
get {
guard let value = assetSum else {
return nil
}
return try? JSONDecoder().decode(([[String]]?).self, from: value)
}
set {
assetSum = try? JSONEncoder().encode(newValue)
}
}
}
Now you can easy set/get values with assetSumValue:
// Create and save
let categories = Categories()
try realm.write {
categories.assetSumValue = [["1", "2", "3"], ["4", "5", "6"]]
realm.add(categories)
}
// Get first element from DB
if let categories = realm.objects(Categories.self).first,
let value = categories.assetSumValue
{
print(value) // Prints: [["1", "2", "3"], ["4", "5", "6"]]
}
In case of encoding/decoding your custom Realm types with complex properties you should implement a custom decoder:
class Categories: Object, Codable {
#Persisted var id: String?
#Persisted var dn: String?
#Persisted private var assetSum: Data?
var assetSumValue: [[String]]? {
get {
guard let value = assetSum else {
return nil
}
return try? JSONDecoder().decode(([[String]]?).self, from: value)
}
set {
assetSum = try? JSONEncoder().encode(newValue)
}
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
super.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode((String?).self, forKey: .id)
dn = try values.decode((String?).self, forKey: .dn)
assetSumValue = try values.decode(([[String]]?).self, forKey: .assetSum)
}
}
How to decode:
let json = """
{
"id": "100",
"dn": "200",
"assetSum": [
["one", "two", "three"],
["four", "five", "six"]
]
}
"""
let categories = try JSONDecoder().decode(Categories.self, from: json.data(using: .utf8)!)
if let value = categories.assetSumValue {
print(value) // Prints [["one", "two", "three"], ["four", "five", "six"]]
}
Happy holidays!
I have a property wrapper that is used to define the key in a key-value pair.
#propertyWrapper
final public class KeyValuePair<T> {
public var wrappedValue: T?
private(set) var key: String
public init(key: String) {
self.key = key
}
}
EDIT:
And using the following protocol, to which all KV models should conform...
protocol Parametrized {
var toParameters: [String: Any] { get }
}
the property wrapper is used in the following fashion:
struct Person: Parametrized {
#KeyValuePair(key: "first_name") var firstName: String?
#KeyValuePair(key: "last_name") var lasteName: String?
#KeyValuePair(key: "age") var age: Int?
#KeyValuePair(key: "isSingle") var isSingle: Bool?
#KeyValuePair(key: "isOpenForDating") var isOpenForDating: Bool?
}
var missUniverse2022 = Person()
missUniverse2022.firstName = "Miss"
missUniverse2022.lastName = "Universe"
missUniverse2022.age = 23
missUniverse2022.isSingle = true // Rad <3
missUniverse2022.isOpenForDating = false // Sad :D
So, the whole idea is to generate a dictionary of type [String: Any] that holds the key-value pairs from that struct using Mirror.
The problem is that the following code is not working (I know why, but I can't figure out the workaround):
extension Parametrized {
var toParameters: [String: Any] {
var parameters = [String: Any]()
for child in Mirror(reflecting: self).children {
/***********************************************
Generic parameter 'T' could not be inferred in cast to 'KeyValuePair'
Explicitly specify the generic arguments to fix this issue
***********************************************/
guard let kvPair = child.value as? KeyValuePair else { continue }
parameters[kvPair.key] = kvPair.wrappedValue
}
return parameters
}
}
So, I turn to the experts here: how to use the mirror logic to get the k-v pairs and put them in a collection?
I really appreciate any help you can provide.
Thanks to the Swift community, I got my answer.
https://forums.swift.org/t/property-wrappers-with-generics-vs-mirroring/54325/4
I want to save the array patientList in UserDefaults. Patient is an custom class so I need to transfer it into Data object, but this doesn't work on Swift 5 like it did before.
func addFirstPatient(){
let newPatient = Patient(name: nameField.text!, number: numberField.text!, resultArray: resultArray, diagnoseArray: diagnoseArray)
let patientList: [Patient] = [newPatient]
let encodeData: Data = NSKeyedArchiver.archivedData(withRootObject: patientList)
UserDefaults.standard.set(encodeData, forKey: "patientList")
UserDefaults.standard.synchronize()
}
struct Patient {
var diagnoseArray: [Diagnose]
var resultArray: [Diagnose]
var name: String
var number: String
init(name: String, number: String, resultArray: [Diagnose], diagnoseArray: [Diagnose]) {
self.diagnoseArray = diagnoseArray
self.name = name
self.number = number
self.resultArray = resultArray
}
}
struct Diagnose{
var name: String
var treatments: [Treatment]
var isPositiv = false
var isExtended = false
init(name: String, treatments: [Treatment]) {
self.name = name
self.treatments = treatments
}
}
struct Treatment {
var name: String
var wasMade = false
init(name: String) {
self.name = name
}
}
This is what the function looks like.
The problem is in the line where I initialize encodeData.
let encodeData: Data = try! NSKeyedArchiver.archivedData(withRootObject: patientList, requiringSecureCoding: false)
This is what Swift suggests but when I try it like this it always crashes and I don't get the error
You cannot use NSKeyedArchiver with structs at all. The objects must be subclasses of NSObject which adopt NSCoding and implement the required methods.
As suggested in the comments Codable is the better choice for example
struct Patient : Codable {
var name: String
var number: String
var resultArray: [Diagnose]
var diagnoseArray: [Diagnose]
}
struct Diagnose : Codable {
var name: String
var treatments: [Treatment]
var isPositiv : Bool
var isExtended : Bool
}
struct Treatment : Codable {
var name: String
var wasMade : Bool
}
let newPatient = Patient(name: "John Doe",
number: "123",
resultArray: [Diagnose(name: "Result", treatments: [Treatment(name: "Treat1", wasMade: false)], isPositiv: false, isExtended: false)],
diagnoseArray: [Diagnose(name: "Diagnose", treatments: [Treatment(name: "Treat2", wasMade: false)], isPositiv: false, isExtended: false)])
let patientList: [Patient] = [newPatient]
do {
let encodeData = try JSONEncoder().encode(patientList)
UserDefaults.standard.set(encodeData, forKey: "patientList")
// synchronize is not needed
} catch { print(error) }
If you want to provide default values for the Bool values you have to write an initializer.
Vadian's answer is correct, you cannot use NSKeyedArchiver with structs. Having all your objects conform to Codable is the best way to reproduce the behavior you are looking for. I do what Vadian does, but I you can also use protocol extensions to make this safer.
import UIKit
struct Patient: Codable {
var name: String
var number: String
var resultArray: [Diagnose]
var diagnoseArray: [Diagnose]
}
struct Diagnose: Codable {
var name: String
var treatments: [Treatment]
var isPositiv : Bool
var isExtended : Bool
}
struct Treatment: Codable {
var name: String
var wasMade : Bool
}
let newPatient = Patient(name: "John Doe",
number: "123",
resultArray: [Diagnose(name: "Result", treatments: [Treatment(name: "Treat1", wasMade: false)], isPositiv: false, isExtended: false)],
diagnoseArray: [Diagnose(name: "Diagnose", treatments: [Treatment(name: "Treat2", wasMade: false)], isPositiv: false, isExtended: false)])
let patientList: [Patient] = [newPatient]
Introduce a protocol to manage the encoding and saving of objects.
This does not have to inherit from Codable but it does for this example for simplicity.
/// Objects conforming to `CanSaveToDisk` have a save method and provide keys for saving individual objects or a list of objects.
protocol CanSaveToDisk: Codable {
/// Provide default logic for encoding this value.
static var defaultEncoder: JSONEncoder { get }
/// This key is used to save the individual object to disk. This works best by using a unique identifier.
var storageKeyForObject: String { get }
/// This key is used to save a list of these objects to disk. Any array of items conforming to `CanSaveToDisk` has the option to save as well.
static var storageKeyForListofObjects: String { get }
/// Persists the object to disk.
///
/// - Throws: useful to throw an error from an encoder or a custom error if you use stage different from user defaults like the keychain
func save() throws
}
Using protocol extensions we add an option to save an array of these objects.
extension Array where Element: CanSaveToDisk {
func dataValue() throws -> Data {
return try Element.defaultEncoder.encode(self)
}
func save() throws {
let storage = UserDefaults.standard
storage.set(try dataValue(), forKey: Element.storageKeyForListofObjects)
}
}
We extend our patient object so it can know what to do when saving.
I use "storage" so that this could be swapped with NSKeychain. If you are saving sensitive data (like patient information) you should be using the keychain instead of UserDefaults. Also, make sure you comply with security and privacy best practices for health data in whatever market you're offering your app. Laws can be a very different experience between countries. UserDefaults might not be safe enough storage.
There are lots of great keychain wrappers to make things easier. UserDefaults simply sets data using a key. The Keychain does the same. A wrapper like https://github.com/evgenyneu/keychain-swift will behave similar to how I use UserDefaults below. I have commented out what the equivalent use would look like for completeness.
extension Patient: CanSaveToDisk {
static var defaultEncoder: JSONEncoder {
let encoder = JSONEncoder()
// add additional customization here
// like dates or data handling
return encoder
}
var storageKeyForObject: String {
// "com.myapp.patient.123"
return "com.myapp.patient.\(number)"
}
static var storageKeyForListofObjects: String {
return "com.myapp.patientList"
}
func save() throws {
// you could also save to the keychain easily
//let keychain = KeychainSwift()
//keychain.set(dataObject, forKey: storageKeyForObject)
let data = try Patient.defaultEncoder.encode(self)
let storage = UserDefaults.standard
storage.setValue(data, forKey: storageKeyForObject)
}
}
Saving is simplified, check out the 2 examples below!
do {
// saving just one patient record
// this saves this patient to the storageKeyForObject
try patientList.first?.save()
// saving the entire list
try patientList.save()
} catch { print(error) }
struct Employee: Codable{
var name: String
}
var emp1 = Employee(name: "John")
let encoder = JSONEncoder()
do {
let data = try encoder.encode(emp1)
UserDefaults.standard.set(data, forKey: "employee")
UserDefaults.standard.synchronize()
} catch {
print("error")
}
I have some model code where I have some Thoughts that i want to read and write to plists. I have the following code:
protocol Note {
var body: String { get }
var author: String { get }
var favorite: Bool { get set }
var creationDate: Date { get }
var id: UUID { get }
var plistRepresentation: [String: Any] { get }
init(plist: [String: Any])
}
struct Thought: Note {
let body: String
let author: String
var favorite: Bool
let creationDate: Date
let id: UUID
}
extension Thought {
var plistRepresentation: [String: Any] {
return [
"body": body as Any,
"author": author as Any,
"favorite": favorite as Any,
"creationDate": creationDate as Any,
"id": id.uuidString as Any
]
}
init(plist: [String: Any]) {
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = UUID(uuidString: plist["id"] as! String)!
}
}
for my data model, then down in my data write controller I have this method:
func fetchNotes() -> [Note] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: Any]] else {
return []
}
return notePlists.map(Note.init(plist:))
}
For some reason the line return notePlists.map(Note.init(plist:)) gives the error 'map' produces '[T]', not the expected contextual result type '[Note]'
However, If I replace the line with return notePlists.map(Thought.init(plist:)) I have no issues. Clearly I can't map the initializer of a protocol? Why not and what's an alternate solution?
If you expect to have multiple types conforming to Note and would like to know which type of note it is stored in your dictionary you need to add an enumeration to your protocol with all your note types.
enum NoteType {
case thought
}
add it to your protocol.
protocol Note {
var noteType: NoteType { get }
// ...
}
and add it to your Note objects:
struct Thought: Note {
let noteType: NoteType = .thought
// ...
}
This way you can read this property from your dictionary and map it accordingly.
This is simplified code that doesn't compile. Is there anyway to make this work in Swift? Thanks.
protocol Person {
var name:String { get }
var age:Int { get }
}
extension Dictionary : Person {
var name: String {
return self["name"] as String
}
var age: Int {
return self["Int"] as Int
}
}
Let me give some context to why I would want to do this.
Lets say I have some person data coming in over the wire as json. As I pass it through JSONSerialization I get a [String:AnyObject] Dictionary back.
So I would like to declare the JSON data interfaces in protocols, make the dictionary objects conform to the protocols and then extract the values from the dictionaries via typed properties, rather then via magic strings and casts. This way the client code would only know about protocol types even though they are implemented as dictionaries behind the curtain.
Not sure it's doable or a good idea, just wanted to try it. But compiler is giving me all sorts of trouble.
I understand you want to encapsulate the logic to link a json to it's model representation.
The suggested solution
First of all I am suggesting another way to achieve what you are looking for instead
Look at this Struct.
struct Person {
let name: String
let age: Int
init?(json: [String:Any]) {
guard let name = json["name"] as? String, age = json["age"] as? Int else { return nil }
self.name = name
self.age = age
}
}
The logic to extract data from the json is encapsulated into its initializer. And if the provided json is not valid the initialization fails. It's safe because it will never crash and it's easy to use.
The direct answer (don't do this at home!)
protocol Person {
var name: String? { get }
var age: Int? { get }
}
extension Dictionary : Person {
private var dictWithStringKeys: [String:Any] {
return reduce([String:Any]()) { (dict, elm) -> [String:Any] in
var dict = dict
if let key = elm.0 as? String {
dict[key] = elm.1
}
return dict
}
}
var name: String? {
return dictWithStringKeys["name"] as? String
}
var age: Int? {
return dictWithStringKeys["age"] as? Int
}
}