条件の数が可変なArray.filter()をやりたかったポエム


動機

ダッシュボードみたいなの作ってて、フィルタリング条件が増えたり減ったりして実装がめんど臭い事が多々あるのであらがってみたかった。MongoのQueryみたいなの妄想したけど、andとかorの条件ネストは諦めた。

そんなに続いてるわけでもないけどコレの続き。

やりたかった事

これに

var data = [
    {b: 500, g:{value: 30}},
    {b: 200},
    {b: 100},
    {b: 300},
    {string: "aiue", g:{value: 200}},
    {string: "aiueo", g:{value: 300}},
]

これを

var conditionParams = [
    {type: "match", "key": ["string"], "value": "ueo"},
    {type: "lt", "key": ["g", "value"], "value": 400},
]

こうすると

oreoreFilter(data, conditionParams, genCondition, true);

ANDでフィルタリングされてこうなる。

// -> [ {string: "aiueo", g:{value: 300}} ]

ゆるふわ実装

// ネストされたオブジェクトをキーで取り出すやつ
function search(data,keys){
    console.log(data, keys);
    return keys.reduce((current, key) => {
        try{
            return current[key]
        } catch(e) {
            return undefined
        }
    }, data)
}

// 検索条件を組み立てるやつ
// あとで挙動を変更したり追加できるようにミドルウェアみを持たせとく
function genCondition(condition){
    try{
        switch(condition.type){
            case "eq": return (value)=>{ return search(value, condition.key) === condition.value;  }
            case "gt": return (value)=>{ return search(value, condition.key) > condition.value; }
            case "lt": return (value)=>{ return search(value, condition.key) < condition.value; }
            case "ge": return (value)=>{ return search(value, condition.key) >= condition.value; }
            case "le": return (value)=>{ return search(value, condition.key) <= condition.value;  }
            case "match": return (value)=>{ return new RegExp(`.*${condition.value}.*`).test(search(value, condition.key)); }
            default: return ()=> false;
        }
    } catch (e){
        return false;
    }
}

// フィルタリング処理
// 対象の配列に対して実際にフィルタリングする関数
function oreoreFilter(data, conditionParams, genCondition, flg){
    const conditions = conditionParams.map(genCondition)

    return data.filter((value)=>{
        return conditions.reduce(
            (sum, condition)=>{       
                // 申し訳程度のAND/OR要素
                if (flg){
                    return sum && condition(value)
                } 
                return sum || condition(value)
            }
        ,flg)
    })
}

テスト

var data = [
    {b: 500, g:{value: 30}},
    {b: 200, g:{value: 500}},
    {b: 200, g:{value: 300}},
    {b: 100},
    {b: 300},
    {string: "aiue", g:{value: 200}},
    {string: "aiueo", g:{value: 500}},
]


var conditionParams = [
    {type: "gt", "key": ["b"], "value": 100}, 
    {type: "lt", "key": ["b"], "value": 700},
    {type: "lt", "key": ["g", "value"], "value": 400},
]

// AND検索
oreoreFilter(data, conditionParams, genCondition, true);
// ->  [ {b: 500, g:{value: 30}},    {b: 200, g:{value: 300}} ]

// OR検索
oreoreFilter(data, conditionParams, genCondition, false);
// ->  [
//         {b: 500, g:{value: 30}},
//         {b: 200, g:{value: 500}},
//         {b: 200, g:{value: 300}},
//         {b: 100},
//         {b: 300},
//         {string: "aiue", g:{value: 200}},
//      ]

まとめ

とりあえず動いてる気がする。