简体   繁体   English

如何有效地比较具有不同键但可能匹配值的2个对象?

[英]How to efficiently compare 2 objects with varying keys but potentially matching values?

I have 2 objects, 1 that comes a SQL database and the other that comes from a JSON REST API. 我有2个对象,1个来自SQL数据库,另一个来自JSON REST API。 Prior to updating a row in the db with new data from the api I want to check not only if the ID already occurs in the db (which would mean an update as opposed to an insert) but also the bulk of the properties. 在使用来自api的新数据更新db中的行之前,我不仅要检查db中是否已经出现ID(这意味着更新而不是插入),还要检查大部分属性。 The reason for this is that when it does update on the database side an additional "lastUpdate" datetime gets added for later processing in PowerBI and if I check only ID then the "lastUpdate" would be fired every single time an entry from the API is already in the DB, even if its properties were not actually updated. 这样做的原因是,当在数据库端更新一个额外的“LASTUPDATE”日期时间获取PowerBI增加了后续处理,如果我只检查ID那么“LASTUPDATE”将每一次从API的入口被触发已经在数据库中,即使其属性实际上没有更新。 A single object (they come in arrays) of each side is as follows: 每一方的单个对象(它们以数组形式出现)如下:


Cases and Notes: 案例和说明:

Important : This section was added as per requested the (now) accepted solution by Nina Scholz. 重要提示 :本节是根据Nina Scholz(现在)接受的解决方案添加的。 Please keep that in mind while reading. 请在阅读时记住这一点。

  • If a path is not entirely traversable on the API side (ie it's parent is null) then it should return Null. 如果路径在API端不可完全遍历(即它的父级为null),则它应返回Null。 An example of this is the path for the DB's side callerLocationID would be callerLocation.id on the API, however when no callerLocation is set then the parent callerLocation is already null thus making the id unreachable 这方面的一个例子是DB的侧面callerLocationID的路径是API上的callerLocation.id ,但是当没有设置callerLocation时,父callerLocation已经为null因此使得id无法访问

  • If a path is entirely traversable on both sides then the values need to be compared. 如果路径在两侧都是完全可穿越的,则需要比较这些值。

  • If the path cannot be traversed on the API side that should be null . 如果无法在API端遍历该路径,则该路径应为null For example I could then compare callerLocationID: null with callerLocation.id as the latter is null and null would be the same as null 例如,我可以再比较callerLocationID: nullcallerLocation.id ,因为后者是nullnull将是相同的null

  • Once all paths are traversed and all values are compared I need to know either "yes they are all identical" ( true ) or "no they are not identical" ( false ). 遍历所有路径并比较所有值后,我需要知道“是的,它们都是相同的”( true )或“不是它们不相同”( false )。 No need to know where they are not identical if they aren't, the entire object is send for the update. 没有必要知道他们都没有,如果他们不相同,整个对象是发送的更新。

  • The paths in the DB side are basically set in stone, if any of these are null that's because they are allowed to be so in the database and that's acceptable 数据库端的路径基本上是一成不变的,如果这些路径中的任何一个都为null ,那是因为它们在数据库中是允许的,并且可以接受

  • All properties on the DB side that end in ID (except incidentID ) refer to a single nested id value in the API. 数据库端以ID结尾的所有属性( incidentID除外)都是指API中的单个嵌套id值。 For example callerID references caller.id , callerBranchID references callerBranch.id and operatorID references operator.id 例如callerID引用caller.idcallerBranchID引用callerBranch.idoperatorID引用operator.id

  • Some exceptions to this are: 一些例外是:

    • impact references impact.name impact引用impact.name
    • urgency references urgency.name urgency参考urgency.name
    • priority references priority.name priority引用priority.name
    • duration references duration.name duration参考duration.name
    • escalationOperator references escalationOperator.id escalationOperator引用escalationOperator.id
  • all the optionalField stuff from the API can be ignored 可以忽略API中的所有optionalField字段内容


// Database Object
{
    incidentID: '0dc1a10f-2899-485a-b814-f72f29c9a15a',
    status: 'secondLine',
    briefDescription: 'Support niet bereikbaar',
    callDate: '2018-04-10T19:01:00.000Z',
    lastUpdate: '2018-04-18T14:02:17.000Z',
    number: 'M1804 021',
    request: '10-04-2018 21:02 Middelkoop, Paul: \nIk kan de servicedesk niet bereiken, telkens in gesprek',
    callerID: '4e723042-0037-4e05-a362-e65c620ba734',
    callerBranchID: 'f66e7804-b57a-4418-a991-997e574ead29',
    callerLocationID: null,
    externalNumber: null,
    categoryID: 'cafb0af8-e43a-4391-ac9e-a0345abbcc4f',
    subcategoryID: 'f56099a9-7c60-45e3-94b4-6555a79d4bd7',
    callTypeID: '04b678a6-791e-4662-9bc8-97573555f15e',
    entryTypeID: 'a9c486fd-a93e-565e-bfeb-17619fafe1a8',
    branchID: null,
    locationID: null,
    impact: null,
    urgency: null,
    priority: null,
    duration: null,
    operatorID: 'a17aba85-13a7-4ac6-8c57-693a512b633e',
    operatorGroupID: 'a17aba85-13a7-4ac6-8c57-693a512b633e',
    supplierID: null,
    targetDate: '2019-04-10T15:30:00.000Z',
    onHold: false,
    onHoldDate: null,
    onHoldDuration: 0,
    feedbackMessage: null,
    feedbackRating: null,
    processingStatus: 'Afgemeld',
    completed: true,
    completedDate: '2018-04-10T19:09:00.000Z',
    closed: true,
    closedDate: null,
    closureCode: null,
    creatorID: '226082ea-8d74-4dee-ae1e-74c33c883792',
    creationDate: '2018-04-10T19:02:34.000Z',
    timeSpent: 0,
    timeSpentFirstLine: 0,
    timeSpentSecondLineAndPartials: 0,
    costs: 0,
    escalationStatus: null,
    escalationReason: null,
    escalationOperator: null,
    modifier: '226082ea-8d74-4dee-ae1e-74c33c883792',
    modificationDate: '2018-04-10T19:12:08.000Z',
    expectedTimeSpent: 0,
    majorCall: false,
    majorCallID: null,
    publishToSSD: false,
    monitored: false,
    archivingReason: null
}

and

// API Object
{
    id: '0dc1a10f-2899-485a-b814-f72f29c9a15a',
    status: 'secondLine',
    number: 'M1804 021',
    request: '10-04-2018 21:02 Middelkoop, Paul: \nIk kan de servicedesk niet bereiken, telkens in gesprek',
    requests: '/tas/api/incidents/id/0dc1a10f-2899-485a-b814-f72f29c9a15a/requests',
    action: '/tas/api/incidents/id/0dc1a10f-2899-485a-b814-f72f29c9a15a/actions',
    attachments: '/tas/api/incidents/id/0dc1a10f-2899-485a-b814-f72f29c9a15a/attachments',
    caller: {
        id: '4e723042-0037-4e05-a362-e65c620ba734',
        dynamicName: 'Mafficioli del Castelletto, Richard',
        branch: {
            id: 'f66e7804-b57a-4418-a991-997e574ead29',
            name: 'Ask Roger! Delft',
            clientReferenceNumber: '',
            timeZone: 'Europe/Amsterdam',
            extraA: null,
            extraB: null
        }
    },
    callerBranch: {
        id: 'f66e7804-b57a-4418-a991-997e574ead29',
        name: 'Ask Roger! Delft',
        clientReferenceNumber: '',
        timeZone: 'Europe/Amsterdam',
        extraA: null,
        extraB: null
    },
    callerLocation: null,
    branchExtraFieldA: null,
    branchExtraFieldB: null,
    briefDescription: 'Support niet bereikbaar',
    externalNumber: '',
    category: {
        id: 'cafb0af8-e43a-4391-ac9e-a0345abbcc4f',
        name: 'Communicatie'
    },
    subcategory: {
        id: 'f56099a9-7c60-45e3-94b4-6555a79d4bd7',
        name: 'Vaste telefonie'
    },
    callType: {
        id: '04b678a6-791e-4662-9bc8-97573555f15e',
        name: 'Klacht'
    },
    entryType: {
        id: 'a9c486fd-a93e-565e-bfeb-17619fafe1a8',
        name: 'Mondeling'
    },
    object: null,
    branch: null,
    location: null,
    impact: null,
    urgency: null,
    priority: null,
    duration: null,
    targetDate: '2019-04-10T15:30:00.000+0000',
    onHold: false,
    onHoldDate: null,
    onHoldDuration: 0,
    feedbackMessage: null,
    feedbackRating: null,
    operator: {
        id: 'a17aba85-13a7-4ac6-8c57-693a512b633e',
        status: 'operatorGroup',
        name: 'Systeembeheer'
    },
    operatorGroup: {
        id: 'a17aba85-13a7-4ac6-8c57-693a512b633e',
        name: 'Systeembeheer'
    },
    supplier: null,
    processingStatus: {
        id: '70b2967d-e248-4ff9-a632-ec044410d5a6',
        name: 'Afgemeld'
    },
    completed: true,
    completedDate: '2018-04-10T19:09:00.000+0000',
    closed: true,
    closedDate: '2018-04-10T19:12:00.000+0000',
    closureCode: null,
    timeSpent: 0,
    timeSpentFirstLine: 0,
    timeSpentSecondLineAndPartials: 0,
    costs: 0,
    escalationStatus: null,
    escalationReason: null,
    escalationOperator: null,
    callDate: '2018-04-10T19:01:00.000+0000',
    creator: {
        id: '226082ea-8d74-4dee-ae1e-74c33c883792',
        name: 'Middelkoop, Paul'
    },
    creationDate: '2018-04-10T19:02:34.000+0000',
    modifier: {
        id: '226082ea-8d74-4dee-ae1e-74c33c883792',
        name: 'Middelkoop, Paul'
    },
    modificationDate: '2018-04-10T19:12:08.000+0000',
    majorCall: false,
    majorCallObject: null,
    publishToSsd: false,
    monitored: false,
    expectedTimeSpent: 0,
    archivingReason: null,
    optionalFields1: {
        boolean1: false,
        boolean2: false,
        boolean3: false,
        boolean4: false,
        boolean5: false,
        number1: 0,
        number2: 0,
        number3: 0,
        number4: 0,
        number5: 0,
        date1: null,
        date2: null,
        date3: null,
        date4: null,
        date5: null,
        text1: '',
        text2: '',
        text3: '',
        text4: '',
        text5: '',
        memo1: null,
        memo2: null,
        memo3: null,
        memo4: null,
        memo5: null,
        searchlist1: null,
        searchlist2: null,
        searchlist3: null,
        searchlist4: null,
        searchlist5: null
    },
    optionalFields2: {
        boolean1: false,
        boolean2: false,
        boolean3: false,
        boolean4: false,
        boolean5: false,
        number1: 0,
        number2: 0,
        number3: 0,
        number4: 0,
        number5: 0,
        date1: null,
        date2: null,
        date3: null,
        date4: null,
        date5: null,
        text1: '',
        text2: '',
        text3: '',
        text4: '',
        text5: '',
        memo1: null,
        memo2: null,
        memo3: null,
        memo4: null,
        memo5: null,
        searchlist1: null,
        searchlist2: null,
        searchlist3: null,
        searchlist4: null,
        searchlist5: null
    }
}

What I have done so far already 到目前为止我已经做了什么

  1. Iterate over the array of API Objects and for every single one check if the ID is the array of Database Objects using Fuse.JS (with a threshold of 0 for only perfect matches) 迭代API对象数组,对于每一个都检查ID是否是使用Fuse.JS的数据库对象数组(阈值为0,仅用于完美匹配)

  2. Use a combination of the result from 1 and the .first, .keys and .pick methods from UnderscoreJS to determine which keys are identical between the 2 objects in the current iteration with the goal of quickly checking those 使用来自1的结果和来自UnderscoreJS的.first,.keys和.pick方法的组合来确定当前迭代中2个对象之间的哪些键是相同的,目的是快速检查这些

    // tdIncidents is the array of objects from the API
    // dbIncidents is the array of objects from the database
    // tdinci is my iterator, consider it the "i" in the for loop
    // at this point it has already been confirmed that both dbIncidents and tdIncidents have at least 1 entry thus using [0] won't give any problems
    const db = _.first(fuse.search(tdIncidents[tdinci].id)),
      td = tdIncidents[tdinci],
      dbKeys = _.keys(dbIncidents[0]),
      tdKeys = _.keys(tdIncidents[0]),
      identicalKeysTd = _.pick(td, (value, key) => dbKeys.includes(key)),
      identicalKeysDb = _.pick(db, (value, key) => tdKeys.includes(key));

identicalKeysTd will then result to: sameKeysTd将导致:

{ status: 'secondLine',
  number: 'M1804 021',
  request: '10-04-2018 21:02 Middelkoop, Paul: \nIk kan de servicedesk niet bereiken, telkens in gesprek',
  briefDescription: 'Support niet bereikbaar',
  externalNumber: '',
  impact: null,
  urgency: null,
  priority: null,
  duration: null,
  targetDate: '2019-04-10T15:30:00.000+0000',
  onHold: false,
  onHoldDate: null,
  onHoldDuration: 0,
  feedbackMessage: null,
  feedbackRating: null,
  processingStatus: { id: '70b2967d-e248-4ff9-a632-ec044410d5a6', name: 'Afgemeld' },
  completed: true,
  completedDate: '2018-04-10T19:09:00.000+0000',
  closed: true,
  closedDate: '2018-04-10T19:12:00.000+0000',
  closureCode: null,
  timeSpent: 0,
  timeSpentFirstLine: 0,
  timeSpentSecondLineAndPartials: 0,
  costs: 0,
  escalationStatus: null,
  escalationReason: null,
  escalationOperator: null,
  callDate: '2018-04-10T19:01:00.000+0000',
  creationDate: '2018-04-10T19:02:34.000+0000',
  modifier:
   { id: '226082ea-8d74-4dee-ae1e-74c33c883792',
     name: 'Middelkoop, Paul' },
  modificationDate: '2018-04-10T19:12:08.000+0000',
  majorCall: false,
  monitored: false,
  expectedTimeSpent: 0,
  archivingReason: null }

identicalKeysDb will result to: 相同的KeysDb将导致:

{ status: 'secondLine',
  briefDescription: 'Support niet bereikbaar',
  callDate: '2018-04-10T19:01:00.000Z',
  number: 'M1804 021',
  request: '10-04-2018 21:02 Middelkoop, Paul: \nIk kan de servicedesk niet bereiken, telkens in gesprek',
  externalNumber: null,
  impact: null,
  urgency: null,
  priority: null,
  duration: null,
  targetDate: '2019-04-10T15:30:00.000Z',
  onHold: false,
  onHoldDate: null,
  onHoldDuration: 0,
  feedbackMessage: null,
  feedbackRating: null,
  processingStatus: 'Afgemeld',
  completed: true,
  completedDate: '2018-04-10T19:09:00.000Z',
  closed: true,
  closedDate: null,
  closureCode: null,
  creationDate: '2018-04-10T19:02:34.000Z',
  timeSpent: 0,
  timeSpentFirstLine: 0,
  timeSpentSecondLineAndPartials: 0,
  costs: 0,
  escalationStatus: null,
  escalationReason: null,
  escalationOperator: null,
  modifier: '226082ea-8d74-4dee-ae1e-74c33c883792',
  modificationDate: '2018-04-10T19:12:08.000Z',
  expectedTimeSpent: 0,
  majorCall: false,
  monitored: false,
  archivingReason: null }
  1. At this point I thought I could check the equality of these two identicalKeys objects using UnderscoreJS's .isEqual ( _.isEqual(identicalKeysDb, identicalKeysTd) ) but to no avail. 在这一点上,我想我可以使用.isEqual.isEqual_.isEqual(identicalKeysDb, identicalKeysTd).isEqual _.isEqual(identicalKeysDb, identicalKeysTd).isEqual _.isEqual(identicalKeysDb, identicalKeysTd) )来检查这两个identicalKeys.isEqual对象_.isEqual(identicalKeysDb, identicalKeysTd)但无济于事。 Besides the fact that I stored some keys directly without "ID" appended in the database (this could be fixed on the DB side), the more pressing issue is that database will give null for values such as the externalNumber , but the API will give '' . 除了直接存储一些键而没有在数据库中附加“ID”的事实(这可以在数据库端修复),更紧迫的问题是数据库将为诸如externalNumber值赋予null ,但API将给出''

Among this latest attempt I have tried many other functions both in ES6, plain JS and UnderscoreJS (too many to mention and code that has long been deleted and no longer available on my "undo" chain) but I cannot find any efficient method at all and I really really do not want to hardcode an enormous if () to check each property against its counterpart. 在最近的尝试中,我尝试了ES6,普通JS和UnderscoreJS中的许多其他功能(太多不能提及和代码已经被删除并且在我的“撤销”链上不再可用)但我找不到任何有效的方法我真的真的不想硬编码一个巨大的if ()来检查每个属性与其对应物。 I do not mind requiring some node package to make this comparison easy so if that is the solution by all means please share it as well. 我不介意要求一些节点包使这个比较容易,所以如果这是解决方案,请分享它。

Those objects that are actually changed I push to an array called existingIncidents which is later returned along with any new incidents. 实际更改的那些对象我推送到一个名为existingIncidents的数组,该数组随后会与任何新事件一起返回。 This happens as follows: 这发生如下:

async filterIncidents() {
    const dbIncidents = await this.getDbIncidents(this.lastFetchTimestamp),
        fuseOpts = {
            'shouldSort': true,
            'findAllMatches': true,
            'threshold': 0,
            'location': 0,
            'distance': 100,
            'maxPatternLength': 36,
            'minMatchCharLength': 36,
            'keys': ['incidentID']
        },
        fuse = new Fuse(dbIncidents, fuseOpts),
        tdIncidents = await this.getTdIncidents(this.lastFetchTimestamp);

    const existIncidents = [],
        newIncidents = [];

    if (!dbIncidents.length) {
        for (const tdinci in tdIncidents) {
            newIncidents.push(tdIncidents[tdinci]);
        }
    } else {
        for (const tdinci in tdIncidents) {
            if (fuse.search(tdIncidents[tdinci].id).length) {
                // The value checking magic I need has to happen here. Some pseudo code:
                // if (values are different) {
                existIncidents.push(tdIncidents[tdinci]);
                // } else {
                // do nothing
                // }
            } else {
                newIncidents.push(tdIncidents[tdinci]);
            }
        }
    }

    return {
        'new': newIncidents,
        'existing': existIncidents
    };
}

Edit: Added the entire function at the bottom 编辑:在底部添加整个功能


Final Edit: I'm dumping a runkit link here to the final solution implementation from Nina Schulz as I have had to adjust it to my exact use case and sharing is caring, maybe it will help someone else in the future. 最终编辑:我在这里将一个runkit链接转移到Nina Schulz的最终解决方案实现,因为我必须将其调整到我的确切用例并且分享是关心,也许它将来会帮助其他人。 Permalink: https://runkit.com/favna/so-compare-objects 永久链接: https//runkit.com/favna/so-compare-objects

This is an approach by using an array for different styled object and the relative path to the properties to compaire of each object. 这是一种方法,通过使用不同样式对象的数组和每个对象的属性的相对路径。

The key functionality is a single function getValue which takes an object and an array of keys to the wanted property and returns a found value or of the last found value of the chain. 关键功能是单个函数getValue ,它将对象和一组键引入所需属性,并返回找到的值或链的最后找到的值。

The other function iterates the given relation object and shows (actually) the two values for compairing and later action, like update or other wanted actions. 另一个函数迭代给定的关系对象,并显示(实际)两个值用于计算和后续操作,如更新或其他想要的操作。

 function getValue(object, keys) { return keys.reduce((o, k) => o && typeof o === 'object' ? o[k] : o, object); } function compaire(objects, relations) { relations.forEach(relation => { var values = relation.map((keys, i) => getValue(objects[i], keys)); console.log(...values); }); } var objectA = { foo: { bar: 42 }, a: { b: { c: 'baz' } }, callerLocation: null }, objectB = { fooBar: 42, nested: { abc: 'bau' }, callerLocationID: null }, objects = [objectA, objectB], relations = [ [['foo', 'bar'], ['fooBar']], [['a', 'b', 'c'], ['nested', 'abc']], [['callerLocation', 'id'], ['callerLocationID']], [['x', 'u'], ['x', 'y']] ]; compaire(objects, relations); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在对象数组中找到一个对象的所有匹配键和值? - How to find all matching keys and values of one object in array of objects? 如何减少包含匹配键/值+变体的对象数组? - How to Reduce An Array of Objects That Contain Matching Keys/Values + Variations? 在对象数组中添加匹配键的值 - Add values of matching keys in array of objects 对象数组中匹配键的总和 - Sum values of matching keys in array of objects 如何比较两个对象但跳过值而只比较键和键类型? - How to compare two objects but skip values and just compare keys and key types? 当一个 object 使用 Lodash 有额外的键时,你如何比较 JSON 对象中的键和值? - How do you compare keys & values in a JSON objects when one object has extra keys using Lodash? 如何在匹配键值的基础上比较两个不同长度和键的数组? - How can i compare two arrays of different length and keys on the basis of matching key values? Javascript:如果对象具有匹配的键,如何用第二个对象的值替换第一个对象的值 - Javascript: How to replace first object's values with values from second object, if objects have matching keys 比较对象数组。 返回匹配的对象键和值 - Compare Object Arrays. Return matching Object keys & values JS:比较对象数组中的值并检测匹配值 - JS: Compare values in Array of Objects and detect matching values
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM