skip to Main Content

I have a computed property of type Parameters in my APIRouter

// MARK: - Parameters
    private var parameters: Parameters? {
        switch self {
        case .searchForDoctors(let doctorsFilter):
            var params: Parameters = ["main_category_id": doctorsFilter.0, "page": doctorsFilter.1, "specialty_id": doctorsFilter.2, "city_id": doctorsFilter.3, "region_id": doctorsFilter.4, "name": doctorsFilter.5, "company_id": doctorsFilter.6, "order_by": doctorsFilter.7]
            return params
        default:
            return nil
        }
    }

some values in the Typealias called doctorsFilter are optional.
currently I have a warning asking me to provide default value for the optional values, and I don’t want to provide default values , I want to check if the value exist to add it, otherwise i will not add the key and the value

how can I safely unwrap the optional values and add it to the parameters dictionary with out saying if let for all optional values?
example:

if let specialtyID = doctorsFilter.2 {
    params["specialty_id"] = specialtyID
}

I don’t want to unwrap it this way as I will check for all optional values and it will take more lines of code

EDIT:-
the DoctorsFilter type is documented, when I initialize an instance of type DoctorsFilter the autocomplete tells me which of them is what, I I’ve thought about making the DoctorsFilter class before but I’m looking for another way if any, maybe a built in reserved word can handle the whole situation! , I want to make it simple as much as possible.
making a function that handles the dictionary and returns it in DoctorsFilter class is an option. I’m thinking of adding this function to the APIRouter, is it fine to add it there? is it the rule of the APIRouter to handle the parameters ? or the APIRouter just interested in taking the parameters and will not handle it ?

2

Answers


  1. There are three primary ways to safely unwrap an optional. You can also provide default values if you wish to unwrap an optional.

    Guard Statement Unwrapping

    var firstString: String?
    
    // In some cases you might performa a break, continue or a return.
    guard let someString = firstString else { return }
    print(someString)
    

    If Let Unwrapping

    var secondString: String?
    var thirdString: String?
    thirdString = "Hello, World!"
    
    // Notice that we are able to use the same "IF LET" to unwrap
    // multiple values. However, if one fails, they all fail. 
    // You can do the same thing with "Guard" statements.
    if let someString = secondString,
       let someOtherString = thirdString {
           print(someString)
           print(someOtherString)
    } else {
           // With this code snippet, we will ALWAYS hit this block.
           // because secondString has no value.
           print("We weren't able to unwrap.")
    }
    

    Default Value Unwrapping

    var fourthString: String?
    // The ?? is telling the compiler that if it cannot be unwrapped,
    // use this value instead.
    print(fourthString ?? "Hello, World")
    

    In Swift it is recommended that anytime you see a ! that you use some form of unwrapping. Swift is very "Type Safe".

    Here’s a resource you can use for Optionals.
    https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html

    Your Solution

    Your solution might look something like this.

    private var parameters: Parameters? {
       switch self {
       case .searchForDoctors(let doctorsFilter):
    
           if let mainCatID = doctorsFilter.0,
              let page = doctorsFilter.1,
              let specialtyID = doctorsFilter.2,
              let cityID = doctorsFilter.3,
              let regionID = doctorsFilter.4,
              let name = doctorsFilter.5,
              let companyID = doctorsFilter.6,
              let orderBy = doctorsFilter.7 {
    
              params: Parameters = ["main_category_id": mainCatID, 
                                    "page": page, 
                                    "specialty_id": specialtyID, 
                                    "city_id": cityID, 
                                    "region_id": regionID, 
                                    "name": name, 
                                    "company_id": companyID, 
                                    "order_by": orderBy]
              return params
        } else {
              //Unable to safely unwrap, return nothing.
              return nil
        }
        default:
            return nil
        }
    }
    
    Login or Signup to reply.
  2. There is no "one line" solution, but you can use KeyPaths to reduce the series of if let ... statements down to a loop.

    Start by creating a struct for your filter rather than using a tuple.

    To facilitate this, we define a protocol for Parameterable – This protocol requires a dictionary that maps parameter names (String) to the property (KeyPath) that holds that parameter name as well as a function to return the parameters dictionary.

    protocol Parameterable {
        var paramNames: [String:KeyPath<Self,String?>] {get}
        func parameters() -> [String:Any]
    }
    

    Use an extension to create a default implementation of the parameters() function, as the code will be the same for all Parameterables. It iterates over the dictionary entries and uses the associated KeyPath to access the relevant property and put it in the output dictionary. If a given property is nil then it simply isn’t added to the output dictionary, because that is how dictionaries work. No need to explicitly check.

    (If you import Alamofire then you can use the typedef Parameters where I have used [String:Any])

    extension Parameterable {
        func parameters() -> [String:Any] {
            var parameters = [String:Any]()
        
            for (paramName,keypath) in self.paramNames {
                parameters[paramName]=self[keyPath:keypath]
            }
            return parameters
        }
    }
    

    Use this protocol to create a DoctorsFilter implementation:

    struct DoctorsFilter: Parameterable {
        var mainCategoryId: String?
        var page: String?
        var specialtyId: String?
        var cityID: String?
        var regionId: String?
        var name: String?
        var companyId: String?
        var orderBy: String?
    
        let paramNames:[String:KeyPath<Self,String?>] = [ 
        "main_category_id":.mainCategoryId,
        "page":.page,
        "specialty_id":.specialtyId,
        "city_id":.cityID,
        "region_id":.regionId,
        "name":.name,
        "company_id":.companyId,
        "order_by":.orderBy]
    }
    
    private var parameters: Parameters? {
        switch self {
            case .searchForDoctors(let doctorsFilter):
                return doctorsFilter.parameters()
            case .someOtherThing(let someOtherThing):
                return someOtherThing.parameters()
            default:
                return nil
            }
        }
    }
    

    The other approach is to simply split your creation of the parameters dictionary into multiple lines; If you assign nil against a dictionary key then there is no key/value pair stored in the dictionary for that key. In this case I have left your tuple approach in place, but you could use the struct (and I strongly suggest you do so)

    private var parameters: Parameters? {
        switch self {
            case .searchForDoctors(let doctorsFilter):
                var params: Parameters()
                params["main_category_id"] = doctorsFilter.0
                params["page"] = doctorsFilter.1
                params["specialty_id"] = doctorsFilter.2
                params["city_id"] = doctorsFilter.3
                params["region_id"] = doctorsFilter.4
                params["name"] = doctorsFilter.5
                params["company_id"] = doctorsFilter.6
                params["order_by"] = doctorsFilter.7
                return params
            default:
                return nil
            }
        }
    

    If we want to handle mixed properties, rather than just optional strings, we need to modify the code slightly. We need to use PartialKeyPath. This makes the code a little more complex since the subscript operator for a PartialKeyPath returns a double optional. This needs to be handled.

    protocol Parameterable {
        var paramNames: [String:PartialKeyPath<Self>] {get}
        func parameters() -> [String:Any]
    }
    
    extension Parameterable {
        func parameters() -> [String:Any] {
            var parameters = [String:Any]()
            for (paramName,keypath) in self.paramNames {
                let value = self[keyPath:keypath] as? Any?
                    if let value = value { 
                        parameters[paramName] = value
                    }
            }
            return parameters
        }
    }
    
    struct DoctorsFilter:Parameterable  {
        var mainCategoryId: String?
        var page: String?
        var specialtyId: String?
        var cityID: Int
        var regionId: String?
        var name: String?
        var companyId: String?
        var orderBy: String?
        let paramNames:[String:PartialKeyPath<Self>] =     
            ["main_category_id":Self.mainCategoryId,
             "page":Self.page,
             "specialty_id":Self.specialtyId,
             "city_id":Self.cityID,
             "region_id":Self.regionId,
             "name":Self.name,
             "company_id":Self.companyId,
             "order_by":Self.orderBy]
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search