Javascript:基于值在不同键上的同一个JS对象内联接

时间:2019-04-27 07:21:05

标签: javascript join lodash

在我的AngularJS 1.3项目中,我尝试使用给定的键连接对象并进行合并。考虑下面的对象(为了更好地理解,将键考虑为表列):

var json = {
    "filteredTask": [{
        "Email_1202_1_1554368387884": "c@c",
        "Number_1202_2_1554368395451": "50000",
        "Number_1202_3_1554368408148": "30000",
        "Text_Field_1202_4_1554368416611": "Developer"
    }, {
        "Email_1202_1_1554368387884": "b@b",
        "Number_1202_2_1554368395451": "25000",
        "Number_1202_3_1554368408148": "20000",
        "Text_Field_1202_4_1554368416611": "QA"
    }, {
        "Email_1202_1_1554368387884": "a@a",
        "Number_1202_2_1554368395451": "22000",
        "Number_1202_3_1554368408148": "20000",
        "Text_Field_1202_4_1554368416611": "Developer"
    }, {
        "Text_Field_1202_1_1554367796776": "c@c",
        "Text_Field_1202_2_1554367980023": "Admin",
        "Text_Field_1202_3_1554367980751": "HR"
    }, {
        "Text_Field_1202_1_1554367796776": "b@b",
        "Text_Field_1202_2_1554367980023": "Non Technical",
        "Text_Field_1202_3_1554367980751": "QA"
    }, {
        "Text_Field_1202_1_1554367796776": "a@a",
        "Text_Field_1202_2_1554367980023": "Technical",
        "Text_Field_1202_3_1554367980751": "Developer"
    }],
    "filter": {
        "joinField": [{
            "formField": {
                "new_key": "Text_Field_1202_1_1554367796776"
            },
            "withFormField": {
                "new_key": "Email_1202_1_1554368387884"
            }
        }, {
            "formField": {
                "new_key": "Text_Field_1202_3_1554367980751"
            },
            "withFormField": {
                "new_key": "Text_Field_1202_4_1554368416611"
            }
        }]
    }
};

请注意,filteredTask包含所有数据,filter包含联接字段关键字信息。

现在,我想像在JSON上执行SQL一样执行联接。例如,加入两个键Text_Field_1202_1_1554367796776Email_1202_1_1554368387884(电子邮件匹配,例如a @ a,b @ b和c @ c),结果应如下所示。

[{
    "Email_1202_1_1554368387884": "c@c",
    "Number_1202_2_1554368395451": "50000",
    "Number_1202_3_1554368408148": "30000",
    "Text_Field_1202_4_1554368416611": "Developer",
    "Text_Field_1202_1_1554367796776": "c@c",
    "Text_Field_1202_2_1554367980023": "Admin",
    "Text_Field_1202_3_1554367980751": "HR"
}, {
    "Email_1202_1_1554368387884": "b@b",
    "Number_1202_2_1554368395451": "25000",
    "Number_1202_3_1554368408148": "20000",
    "Text_Field_1202_4_1554368416611": "QA",
    "Text_Field_1202_1_1554367796776": "b@b",
    "Text_Field_1202_2_1554367980023": "Non Technical",
    "Text_Field_1202_3_1554367980751": "QA"
}, {
    "Email_1202_1_1554368387884": "a@a",
    "Number_1202_2_1554368395451": "22000",
    "Number_1202_3_1554368408148": "20000",
    "Text_Field_1202_4_1554368416611": "Developer",
    "Text_Field_1202_1_1554367796776": "a@a",
    "Text_Field_1202_2_1554367980023": "Technical",
    "Text_Field_1202_3_1554367980751": "Developer"
}]

现在,如果我用Text_Field_1202_1_1554367796776(电子邮件)和Email_1202_1_1554368387884(用Text_Field_1202_3_1554367980751(指定))执行两次联接,例如Text_Field_1202_4_1554368416611,那么考虑到电子邮件和名称都匹配:

[{
    "Email_1202_1_1554368387884": "b@b",
    "Number_1202_2_1554368395451": "25000",
    "Number_1202_3_1554368408148": "20000",
    "Text_Field_1202_4_1554368416611": "QA",
    "Text_Field_1202_1_1554367796776": "b@b",
    "Text_Field_1202_2_1554367980023": "Non Technical",
    "Text_Field_1202_3_1554367980751": "QA"
}, {
    "Email_1202_1_1554368387884": "a@a",
    "Number_1202_2_1554368395451": "22000",
    "Number_1202_3_1554368408148": "20000",
    "Text_Field_1202_4_1554368416611": "Developer",
    "Text_Field_1202_1_1554367796776": "a@a",
    "Text_Field_1202_2_1554367980023": "Technical",
    "Text_Field_1202_3_1554367980751": "Developer"
}]

我为此尝试了lodash library实用工具。

angular.forEach(json.filter.joinField, function (jField) {
    var obj1 = _.filter(json.filteredTask, function (o1) {
        return o1[jField.formField.new_key];
    });

    var obj2 = _.filter(json.filteredTask, function (o2) {
        return o2[jField.withFormField.new_key];
    });

    // for intersaction data
    var result = _.intersectionWith(_.cloneDeep(obj1), obj2, function (x, y) {
        //Improvement needed here: when I print, it shows different keys and values and generated output differently.
        return x[jField.formField.new_key] === y[jField.withFormField.new_key] && _.assign(x, y);
    });

    json.filteredTask = result;
});
console.log(json);

让我知道我们是否可以通过其他任何方式实现这一目标,或者可以改进这一方法。

1 个答案:

答案 0 :(得分:2)

以下是您可以采取的步骤:

  1. 从“ joinField”数据中提取连接条件,以便获得简单的[a,b]对
  2. 对于每一行,确定它在连接的哪一侧:如果该行中存在第一个连接条件的左侧字段,则该字段位于左侧。
  3. 从连接条件中列出的字段中创建一个组合键-记录在一边
  4. 通过组合键对行进行分组:左侧和右侧将进行分组
  5. 从左组和右组中加入相同键的行:这是笛卡尔积
  6. 连接所有这些结果

您可以在ES2018中做到这一点:

function join(obj) {
    // Extract join conditions
    const keys = obj.filter.joinField.map(pair => 
                     [pair.withFormField.new_key, pair.formField.new_key]);
    const grouped = [new Map, new Map]; // groups for left & right side
    const result = [];
    // Put rows in buckets based on their join fields
    for (const row of obj.filteredTask) {
        // Determine which "side" of the join this row is on
        let side = +(keys[0][1] in row);
        // Create single key value:
        let key = JSON.stringify(keys.map(pair => row[pair[side]]));
        if (!grouped[side].has(key)) grouped[side].set(key, []);
        grouped[side].get(key).push(row);
    }
    // Perform the join
    for (const [key, rows1] of grouped[0]) {
        // pick up rows from the other side that share the same key (intersection)
        const rows2 = grouped[1].get(key);
        if (!rows2) continue;
        for (const row1 of rows1) {
            for (const row2 of rows2) result.push({...row1, ...row2});
        }
    }
    return result;
}

var data = {"filteredTask": [{"Email_1202_1_1554368387884": "c@c","Number_1202_2_1554368395451": "50000","Number_1202_3_1554368408148": "30000","Text_Field_1202_4_1554368416611": "Developer"}, {"Email_1202_1_1554368387884": "b@b","Number_1202_2_1554368395451": "25000","Number_1202_3_1554368408148": "20000","Text_Field_1202_4_1554368416611": "QA"}, {"Email_1202_1_1554368387884": "a@a","Number_1202_2_1554368395451": "22000","Number_1202_3_1554368408148": "20000","Text_Field_1202_4_1554368416611": "Developer"}, {"Text_Field_1202_1_1554367796776": "c@c","Text_Field_1202_2_1554367980023": "Admin","Text_Field_1202_3_1554367980751": "HR"}, {"Text_Field_1202_1_1554367796776": "b@b","Text_Field_1202_2_1554367980023": "Non Technical","Text_Field_1202_3_1554367980751": "QA"}, {"Text_Field_1202_1_1554367796776": "a@a","Text_Field_1202_2_1554367980023": "Technical","Text_Field_1202_3_1554367980751": "Developer"}],"filter": {"joinField": [{"formField": {"new_key": "Text_Field_1202_1_1554367796776"},"withFormField": {"new_key": "Email_1202_1_1554368387884"}}, {"formField": {"new_key": "Text_Field_1202_3_1554367980751"},"withFormField": {"new_key": "Text_Field_1202_4_1554368416611"}}]}};

const result = join(data);

console.log(result);

在ES5中:

function join(obj) {
    // Extract join conditions
    var keys = obj.filter.joinField.map(pair => 
                     [pair.withFormField.new_key, pair.formField.new_key]);
    var grouped = [{}, {}]; // groups for left & right side
    var result = [];
    // Put rows in buckets based on their join fields
    obj.filteredTask.forEach(function (row) {
        // Determine which "side" of the join this row is on
        var side = +(keys[0][1] in row);
        // Create single key value:
        var key = JSON.stringify(keys.map(function (pair) {
            return row[pair[side]];
        }));
        if (!grouped[side][key]) grouped[side][key] = [];
        grouped[side][key].push(row);
    });
    // Perform the join
    for (var key in grouped[0]) {
        var rows1 = grouped[0][key];
        // pick up rows from the other side that share the same key (intersection)
        var rows2 = grouped[1][key];
        if (!rows2) continue;
        rows1.forEach(function (row1) {
            rows2.forEach(function (row2) {
                result.push(Object.assign({}, row1, row2)); 
            });
        });
    }
    return result;
}

var data = {"filteredTask": [{"Email_1202_1_1554368387884": "c@c","Number_1202_2_1554368395451": "50000","Number_1202_3_1554368408148": "30000","Text_Field_1202_4_1554368416611": "Developer"}, {"Email_1202_1_1554368387884": "b@b","Number_1202_2_1554368395451": "25000","Number_1202_3_1554368408148": "20000","Text_Field_1202_4_1554368416611": "QA"}, {"Email_1202_1_1554368387884": "a@a","Number_1202_2_1554368395451": "22000","Number_1202_3_1554368408148": "20000","Text_Field_1202_4_1554368416611": "Developer"}, {"Text_Field_1202_1_1554367796776": "c@c","Text_Field_1202_2_1554367980023": "Admin","Text_Field_1202_3_1554367980751": "HR"}, {"Text_Field_1202_1_1554367796776": "b@b","Text_Field_1202_2_1554367980023": "Non Technical","Text_Field_1202_3_1554367980751": "QA"}, {"Text_Field_1202_1_1554367796776": "a@a","Text_Field_1202_2_1554367980023": "Technical","Text_Field_1202_3_1554367980751": "Developer"}],"filter": {"joinField": [{"formField": {"new_key": "Text_Field_1202_1_1554367796776"},"withFormField": {"new_key": "Email_1202_1_1554368387884"}}, {"formField": {"new_key": "Text_Field_1202_3_1554367980751"},"withFormField": {"new_key": "Text_Field_1202_4_1554368416611"}}]}};

var result = join(data);

console.log(result);