Home > Mobile >  Unable to properly iterate and destructure using recursive-iterator
Unable to properly iterate and destructure using recursive-iterator

Time:10-01

I'm having trouble using RecursiveIterator.

Basically, I need to iterate over a graph of objects to see what properties mays have been modified to be able to insert an history in a database...

So, I wrote the following Typescript code to play with in VS Code:

import RecursiveIterator = require("recursive-iterator");

const old = {
  vehicleId: 1,
  vehicleName: "Ol",
  modifiedProperties: [],
  device: {
    deviceId: 12,
    hardwareId: "12345",
    modifiedProperties: [],
  },
};

const n = {
  vehicleId: 1,
  vehicleName: "Ol2",
  modifiedProperties: ["vehicleName"],
  device: {
    deviceId: 12,
    hardwareId: "54321",
    modifiedProperties: ["hardwareId"],
  },
};

console.log("ouf");

var iteratorOnDeepClone = new RecursiveIterator(old, undefined, true);
var iteratorOnModifiedObject = new RecursiveIterator(n, undefined, true);

while (true) {
  let { value: valueOnModifiedObject, done: doneOnModifiedObject } =
    iteratorOnModifiedObject.next();
  let { value: valueOnDeepClone /*, done: doneOnDeepClone*/ } =
    iteratorOnDeepClone.next();

  if (valueOnDeepClone == undefined) {
    console.error("have a kitkat");
  }

  let {
    parent: parentOnDeepClone,
    node: nodeOnDeepClone,
    key: keyOnDeepClone,
    path: pathOnDeepClone,
    deep: deepOnDeepClone,
  } = valueOnDeepClone;
  let {
    parent: parentOnModifiedObject,
    node: nodeOnModifiedObject,
    key: keyOnModifiedObject,
    path: pathOnModifiedObject,
    deep: deepOnModifiedObject,
  } = valueOnModifiedObject;

  if (nodeOnModifiedObject.modifiedProperties != undefined) {
    for (var prop of nodeOnModifiedObject.modifiedProperties) {
      const oldValue: any = nodeOnDeepClone[prop];
      console.log(prop, oldValue);
    }
  }
  if (doneOnModifiedObject) {
    console.log("done!");
    break;
  }
}

Right now it fails (on line 41) because a some point the iteratorOnDeepClone.next() returned object is invalid:

Exception has occurred: TypeError: Cannot destructure property 'parent' of 'valueOnDeepClone' as it is undefined.
  at Object.<anonymous> (d:\temp\sandbox\out\iterator_sample.js:33:19)
    at Module._compile (internal/modules/cjs/loader.js:1137:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)
    at Module.load (internal/modules/cjs/loader.js:985:32)
    at Function.Module._load (internal/modules/cjs/loader.js:878:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

package.json

{
  "name": "playground",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "recursive-iterator": "^3.3.0"
  },
  "devDependencies": {
    "ts-node": "^10.2.1",
    "tsconfig-paths": "^3.11.0",
    "typescript": "4.2.4"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "out",
    "sourceMap": true
  }
}

launch.json

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "pwa-node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/iterator_sample.ts",
            "preLaunchTask": "tsc: build - tsconfig.json",
            "outFiles": [
                "${workspaceFolder}/out/**/*.js"
            ]
        }
    ]
}

EDIT 1: Found he breaks and stopped the train. The problem lies with te modifiedProperties array that is considered as an object when not empty. So the iterators are desynchronized.

CodePudding user response:

I'd suggest that this is a problematic technique to find differences.

If you're trying to scan them in parallel, it's quite possible that you'll find differences that have only to do with the order that properties were added and not their values. Presumably that's not what you want.

User Mulan has an excellent answer explaining one good way to find differences, giving you a nested result object showing in excellent detail where things have changed. Here is a somewhat different technique, which flattens the objects using '.'-separated keys to delineate paths.

const compare = (a, b, first = transform (a), second = transform (b)) => 
  uniq (Object .keys (first) .concat (Object .keys (second)))
    .filter ((k) => first [k] !== second [k]) 
    .reduce ((a, k) => ((a [k] = {left: first [k], right: second [k]}), a), {})

const transform = (o) =>
  pathEntries (o) 
    .reduce ((a, [k, v]) => ((a[k .join ('.')] = v), a), {})

const pathEntries = (obj) =>
  Object (obj) === obj
    ? Object .entries (obj) .flatMap (
        ([k, x]) => pathEntries (x) .map (
          ([p, v]) => [[Array .isArray (obj) ? Number (k) : k, ... p], v]
        )
      ) 
    : [[[], obj]]

const uniq = (xs) => 
  [... new Set (xs)]

const orig = {
  foo: 42,
  bar: {baz: 99, qux: {corge: [1, 2, 3]}},
  grault: 'abc',
}

const modified = {
  foo: 42,
  bar: {baz: 99, qux: {corge: [1, 2, 4]}},
  waldo: 'xyz'
}

console .log (compare (orig, modified))
.as-console-wrapper {max-height: 100% !important; top: 0}

This works by first turning an object like this:

const orig = {
  foo: 42,
  bar: {baz: 99, qux: {corge: [1, 2, 3]}},
  grault: 'abc',
}

into

[
  [["foo"], 42],
  [["bar", "baz"], 99],
  [["bar", "qux", "corge", 0] ,1],
  [["bar", "qux", "corge", 1], 2],
  [["bar", "qux", "corge", 2], 3],
  [["grault"], "abc"]
]

using the utility pathEntries function and then into

{
  "foo": 42,
  "bar.baz": 99,
  "bar.qux.corge.0": 1,
  "bar.qux.corge.1": 2,
  "bar.qux.corge.2": 3,
  "grault": "abc"
}

inside transform.

We do this for our first and second objects, then simply find the keys in the output object where they differ and report the differing values between the two objects in a single object structure.


Mulan's technique is more flexible, and captures the differences in a more structured manner. This one's flat output, though, may be nicer for storing in a database, depending on the style of db you have.

  • Related