Creating dynamic collection filter from JSON?


#1

I’m looking to get my feet wet in Rust by working on a small service that’s returning some data, based on some sorting and filter options, that are passed in as a json structure.

Given a Vector data and this rather simple json example

{
        "filters": {
            "year": {
                "$and": [{
                    "$gte": 1990
                }, {
                    "$lte": 2000
                }]
            }
        }
    }

I’m looking to generate this closure in the filter function:

data.iter().filter(|m| m.year >= gte && m.year <= lte)

To handle the json data, I’m looking to use the serde_json crate. But in regards to actually creating that filter, I’m a little lost on how to best approach this. I will expect nested filters, and the usual ors, nots, eqs, and so on…


#2

While I did come up with a solution and would like to politely request a code review, I’m still looking for an overall better approach on how to handle this type of challenge.


#3

I don’t have a stackexchange account, so posting it here.

I have rewitten construct_filter and one_point_of_success a bit:

impl RecsysObject {
    fn construct_filter(&self, movie: &Media) -> bool {
        if self.filters.is_none() {
            return true;
        }
        let f = self.filters.clone().unwrap();
        
        if f.get("year").is_none() {
            return true;
        }
        let yf = f.get("year").unwrap();
        
        if let Some(year) = yf.eq {
            return movie.year == year;
        }
        if yf.and.is_none() {
            return true;
        }
        
        let rangefilter = yf.and.clone().unwrap();
        for rf in rangefilter {
            match rf {
                LogicFilter::LT(val) if movie.year >= val {
                    return false;
                }
                LogicFilter::GT(val) => if movie.year <= val {
                    return false;
                }
                _ => {}
            }
        }
        true
    }
}

#[post("/", data = "<object>")]
fn one_point_of_success(allmovies: State<Vec<Media>>, object: JSON<RecsysObject>) -> JSON<Value> {
    let mut results = allmovies
                    .iter()
                    .filter(|m| object.construct_filter(m))
                    .collect::<Vec<_>>();
    if let Some(v) = object.sort.clone() {
        if let Some(hmap) = v.get(0) {
            match hmap.get("property") {
                None => {
                    if hmap.get("type").unwrap() == "popularity" {
                        match hmap.get("order") {
                            Some(order) => {
                                if order == "ascending" {
                                    results.sort_by(|a, b| a.rating.cmp(&b.rating));
                                }
                            }
                            _ => {} // already sorted
                        }
                    }
                }
                _ => {
                    if hmap.get("property").unwrap() == "metadata.releaseDate" {
                        match hmap.get("order") {
                            Some(order) => {
                                if order == "ascending" {
                                    results.sort_by(|a, b| a.year.cmp(&b.year));
                                } else {
                                    results.sort_by(|a, b| b.year.cmp(&a.year));
                                }
                            }
                            _ => {
                                results.sort_by(|a, b| b.year.cmp(&a.year));
                            }
                        }
                    }
                }
            }
        }
    }
    JSON(json!(results.iter().skip(object.page.unwrap_or(0) * object.page_size.unwrap_or(10)) // first page is page 0
                    .take(object.page_size.unwrap_or(10)).collect::<Vec<_>>()))

}

#4

Thank you for improving the readability. While the construct_filter is now a lot cleaner, I’ve noticed a significant drop in performance.