skip to Main Content

I have this data structure:

const data = [
    { name: 'John', age: 36, color: {red: 243, green: 22, blue: 52} },
    { name: 'Jane', age: 28, color: {red: 23, green: 62, blue: 15} },
    { name: 'Lisa', age: 42, color: {red: 89, green: 10, blue: 57} }
]

And I want to filter it based on a list of conditions, like this (filter property is mapped to a predicate function from ramda):

const definitions = [
    {
        id: 'name',
        filter: 'includes',
        path: [],
    },
    {
        id: 'age',
        filter: 'gte',
        path: [],
        value: 36
    }
]

Using this code works fine for properties that is directly on the object:

const Operations = {
    includes,
    equals,
    gte
}

const filters = reduce((a, c) => {
    a[c.id] = c.value ? Operations[c.filter](c.value) : always(true);
    return a;
}, {}, definitions)

filter(where(filters), data);

See Ramda playground link

Problem is, I want to introduce other filter conditions that operate on nested structures. For that I introduced a path property, that would take an array of strings that represents the path where the property is located on the object:

const definitions = [
    {
        id: 'name',
        filter: 'includes',
        path: [],
    },
    {
        id: 'age',
        filter: 'gte',
        path: [],
        value: 36
    },
    {
        id: 'red',
        filter: 'gte',
        path: ['color', 'red'],
        value: 40
    },
    {
        id: 'blue',
        filter: 'gte',
        path: ['color', 'blue']
    }
]

This should result in (filters only take effect when there is a value exist in the definition):

{
    age: 36,
    color: {
        blue: 52,
        green: 22,
        red: 243
    },
    name: "John"
}

However, I can’t make it work with where.
I could pre-map the array to bring the nested
properties to the uppermost level of the objects, but I guess there is a more elegant way.

2

Answers


  1. Chosen as BEST ANSWER

    I found out eventually, just have to drop where and change it to use allPass:

    const filtered = filter(allPass(values(activeFilters)), data);
    

    And change the reducer to:

    reduce((a, c) => {
        a[c.id] = (val) => {
            const getPath = c.path ? path([...c.path, c.id]) : path([c.id])
            return c?.value ? FilterOperations[c.active](c.value)(getPath(val)) : always(true);
        }
        return a;
    }, {}, definitions)
    
    

  2. I’d suggest pathSatisfies but you’d have to switch gte for lte.

    const createPredicates = R.reduce((res, def) => {
      const predicate = def.value 
        ? R.pathSatisfies(R[def.filter](def.value), def.path)
        : R.T;
    
      return [...res, predicate];
    }, []);
    
    const data = [
      { name: 'John', age: 36, color: {red: 243, green: 22, blue: 52} },
      { name: 'Jane', age: 28, color: {red: 23, green: 62, blue: 15} },
      { name: 'Lisa', age: 42, color: {red: 89, green: 10, blue: 57} }
    ];
    
    const definitions = [
      {
        id: 'name',
        filter: 'includes',
        path: [],
      },
      {
        id: 'age',
        filter: 'lte',
        path: ['age'],
        value: 36
      },
      {
        id: 'red',
        filter: 'lte',
        path: ['color', 'red'],
        value: 40
      },
      {
        id: 'blue',
        filter: 'lte',
        path: ['color', 'blue']
      }
    ];
    
    const fn = R.filter(
      R.allPass(createPredicates(definitions)),
    );
    
    console.log(
      fn(data),
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.29.0/ramda.js" integrity="sha512-J4Em3sC41YRNdTfPiA2QTHFVyPP7Qqpac9s+sb4p4bdfZfiJai2s1EXZcDB+V0r23kYc42p/cFVlrbz7/1Zwjg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search