wvteijlingen / Spine

A Swift library for working with JSON:API APIs. It supports mapping to custom model classes, fetching, advanced querying, linking and persisting.
MIT License
264 stars 109 forks source link

Passing multiple filter values for a key #140

Closed markst closed 7 years ago

markst commented 7 years ago

I wish to pass multiple filters for query filter. It seems the following produces wrongly formatted url.

query.addPredicateWithField("state", value:
    [PositionState.completed.rawValue,
     PositionState.approved.rawValue,
     PositionState.disputed.rawValue], type: .equalTo)

Produces

&filter[state]=(
    completed,
    approved,
    disputed
)&sort=created-at...
markst commented 7 years ago

http://jsonapi.org/format/#fetching-sparse-fieldsets

markst commented 7 years ago

This is due to router's queryItemForFilter using String(describing:)

My current resolution is to override queryItemForFilter in my JSONAPIRouter subclass.

override func queryItemForFilter(on key: String, value: Any?, operatorType: NSComparisonPredicate.Operator) -> URLQueryItem {
    var stringValue = value ?? "null"
    if let valueArray = value as? [String] {
        stringValue = valueArray.joined(separator: ",")
        return URLQueryItem(name: "filter[\(key)][]", value: String(describing: stringValue))
    } else {
        return URLQueryItem(name: "filter[\(key)]", value: String(describing: stringValue))
    }
}
wvteijlingen commented 7 years ago

Good point. I think we can make some improvement here to support array values. From the top of my head (not tested):

let stringValue: String
if let valueArray = value as? Array {
    stringValue = valueArray.map { String(describing: $0) }.joined(separator: ",")
} else if let value = value {
    stringValue = String(describing: value)
} else {
    stringValue = "null"
}
return URLQueryItem(name: "filter[\(key)]", value: stringValue)
wvteijlingen commented 7 years ago

I've updated this on master. Should work out of the box now.

markst commented 7 years ago

Thanks @wvteijlingen this works fine when passing array as value but not when adding multiple predicates such as:

selectedJobCategories.forEach { (jobCategory) in
    query.addPredicateWithKey("job-category-id", value: jobCategory.id, type: .equalTo)
}

I can simply override JSONAPIRouter.appendQueryItem() in my router subclass to avoid this.

markst commented 7 years ago

Might also add that Routing.appendQueryItem() doesn't allow for duplicate query names. Perhaps this isn't JSON:API conforming but our backend supports passing params as follows: ?filter[suburb][]=a&filter[suburb][]=b