[英]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: null
与callerLocation.id
,因为后者是null
和null
将是相同的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.id
, callerBranchID
引用callerBranch.id
和operatorID
引用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 到目前为止我已经做了什么
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,仅用于完美匹配)
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 }
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 ''
. 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.