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

时间:2018-04-18 13:48:48

标签: javascript arrays json node.js object

我有2个对象,1个来自SQL数据库,另一个来自JSON REST API。在使用来自api的新数据更新db中的行之前,我不仅要检查db中是否已经出现ID(这意味着更新而不是插入),还要检查大部分属性。这样做的原因是,当在数据库端进行更新时,会添加一个额外的“lastUpdate”日期时间,以便以后在PowerBI中进行处理,如果我只检查ID,那么每次都会触发“lastUpdate”来自API的条目已经在数据库中,即使其属性未实际更新。每一方的单个对象(它们以数组形式出现)如下:

案例和说明:

重要:此部分是根据Nina Scholz(现在)已接受的解决方案添加的。请在阅读时记住这一点。

  • 如果路径在API端不可完全遍历(即它的父级为空),则它应返回Null。这方面的一个例子是数据库的侧面callerLocationID在API上的路径callerLocation.id,但是当没有设置callerLocation时,父callerLocation已经null,因此{{1}无法访问

  • 如果路径两侧都可以完全遍历,则需要比较这些值。

  • 如果无法在API端遍历应该为id的路径。例如,我可以将nullcallerLocationID: null进行比较,因为后者为callerLocation.idnullnull

  • 相同
  • 遍历所有路径并比较所有值后,我需要知道“是的,它们都是相同的”(null)或“不是它们不相同”(true) 。不需要知道其中它们不相同如果不相同,则发送整个对象进行更新。

  • 数据库方面的路径基本上是一成不变的,如果其中任何一个是false,那是因为它们在数据库中是允许的,并且可以接受

  • 数据库端以null结尾的所有属性(ID除外)都是指API中的单个嵌套incidentID值。例如id引用callerIDcaller.id引用callerBranchIDcallerBranch.id引用operatorID

  • 有些例外情况是:

    • operator.id引用impact
    • impact.name引用urgency
    • urgency.name引用priority
    • priority.name引用duration
    • duration.name引用escalationOperator
  • 可以忽略API中的所有escalationOperator.id内容

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
}

到目前为止我已经做了什么

  1. 迭代API对象数组,并且对于每一个都检查ID是否是使用Fuse.JS的数据库对象数组(阈值为0,仅用于完美匹配)

  2. 使用来自1的结果和来自UnderscoreJS的.first,.keys和.pick方法的组合来确定当前迭代中2个对象之间的哪些键是相同的,目的是快速检查这些

  3. // 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
        }
    }
    

    相同的KeysTd将导致:

        // 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));
    

    相同的KeysDb将导致:

    { 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 }
    
    1. 此时我认为我可以使用UnderscoreJS的{ 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)检查这两个.isEqual对象是否相等,但无济于事。除了直接存储一些键而没有在数据库中附加“ID”的事实(这可以在数据库端修复),更紧迫的问题是数据库将为_.isEqual(identicalKeysDb, identicalKeysTd)等值提供null但是API会给externalNumber
    2. 在最近的尝试中,我在ES6,普通JS和UnderscoreJS中尝试了很多其他功能(太多不能提及和代码已经被删除,在我的“撤销”链上不再可用)但我找不到任何有效的功能方法,我真的真的不想硬编码一个巨大的''来检查每个属性与其对应物。我不介意要求一些节点包使这个比较容易,所以如果这是解决方案,请分享它。

      那些实际更改的对象我推送到一个名为if ()的数组,后来随后会返回任何新事件。这发生如下:

      existingIncidents

      编辑:在底部添加了整个功能

      最终编辑:我在这里将一个runkit链接转移到Nina Schulz的最终解决方案实现,因为我必须将其调整到我的确切用例并且分享是关心,也许它将来会帮助其他人。 永久链接:https://runkit.com/favna/so-compare-objects

1 个答案:

答案 0 :(得分:1)

这是一种方法,通过对不同样式的对象使用数组以及每个对象的属性的相对路径。

关键功能是单个函数getValue,它将对象和一组键引入所需属性,并返回找到的值或链的最后找到的值。

另一个函数迭代给定的关系对象,并显示(实际上)两个值用于计算和后续操作,如更新或其他想要的操作。



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);