0

I have an object obj which has n number of possible properties

lets say some of them are known,

const someKnownProps = ["props.abc", "xyz"]; // or more

I want to know if obj has other than known properties in it.

To clarify:

obj can look like this:

obj = {
   props: {
     abc: {
       def: 1
     },
     ghi: {
       jkl: 2
     }
   },
   xyz: 3
}

Doing Object.keys only return first level children, in this case it will return props not props.abc

Khurram W. Malik
  • 2,660
  • 2
  • 20
  • 27
  • 2
    What is the wanted result? Just "yes" or "no", or a list of those other properties? How about the properties in the prototype(chain)? – Teemu Aug 07 '22 at 13:37
  • You may want to take a look at [this link](https://stackoverflow.com/questions/22658488/object-getownpropertynames-vs-object-keys) as well. – c0m1t Aug 07 '22 at 13:51
  • The task is to know if *obj* has other than known properties, if it has other property/properties result should be **yes** In my case I can ignore prototype chain, a solution explaining inherited properties will be appreciated! – Khurram W. Malik Aug 07 '22 at 13:54
  • Even Object.getOwnPropertyNames returns *props* not *props.abc* – Khurram W. Malik Aug 07 '22 at 14:06
  • Ignoring what constitutes a valid [object property accessor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors) for a moment (although it is critical to the details of this problem): In your example data, you show `props.abc` in the "known props", but don't include `props`. If an object has a property at `obj.props.abc`, then it will certainly have `obj.props`, so — given the details of your case — the result would always be "the object has more properties than provided". – jsejcksn Aug 07 '22 at 14:36

3 Answers3

2

You can use Object.keys to get all keys and filter the keys which aren't included in the someKnownProps array.

const obj = {
"props.abc": 1,
"xyz": 2,
"three": 3,
"four": 4,
}

const someKnownProps = ["props.abc", "xyz"]; // or more

const unknownKeys = Object.keys(obj).filter(key => !someKnownProps.includes(key))

console.log(unknownKeys)
Mina
  • 14,386
  • 3
  • 13
  • 26
  • 1
    If you need a bool, you can use `some`: `Object.keys(obj).some(key => !someKnownProps.includes(key))` – Idrizi.A Aug 07 '22 at 13:42
  • 1
    props here can also be an object inside obj doing Object.keys will return "props" not its nested children, im updating my question to clarify this case – Khurram W. Malik Aug 07 '22 at 13:42
1

There are two (unrelated) tasks involved in this question:

  1. Traversal of an object's properties
  2. Comparison of a set of traversed object properties to a list of strings representing dot-notation-formatted object property accessors

While I'm sure the former has been previously discussed on SO, I'll provide an implementation of such an algorithm below in order to address the details of this question.

This is essentially a specific case of recursion where each cycle starts with these inputs:

  • an object
  • a dot-notation-formatted path
  • a Set of existing such paths

The code below includes inline comments explaining what's happening, and there are some console.log statements at the end to help you visualize some example results based on the data in your question. If something is unclear after reviewing the code, feel free to leave a comment.

'use strict';

/** @returns whether value is a non-null, non-array object */
function isObject (value) {
  return value !== null && typeof value === 'object' && !Array.isArray(value);
}

/** @returns the enumerable (optionally including inherited) keys of an object */
function getKeys (obj, includeInherited = false) {
  if (!includeInherited) return Object.keys(obj);
  const keys = new Set();
  let o = obj;
  while (o !== null) {
    for (const key of Object.keys(o)) keys.add(key);
    o = Object.getPrototypeOf(o);
  }
  return [...keys];
}

/**
 * @returns an array of strings representing all traversible branches
 * of child objects, each formatted as a combined path of dot-notation
 * property accessors
 */
function findObjectPaths (
  obj,
  {
    includeInherited = false,
    currentPath = '',
    paths = new Set(),
    skipReturn = false,
  } = {},
) {
  for (const key of getKeys(obj, includeInherited)) {
    // Append the current dot-notation property accessor
    // to the existing path of this object:
    const path = `${currentPath}.${key}`;
    // Add it to the set:
    paths.add(path);
    const o = obj[key];
    // Recurse if the child value is an object:
    if (isObject(o)) {
      findObjectPaths(o, {
        includeInherited,
        currentPath: path,
        paths,
        skipReturn: true,
      });
    }
  }

  // If this is not a sub-cycle (it's the top-level invocation), then convert
  // the set to an array and remove the first "." from each string
  if (!skipReturn) return [...paths].map(p => p.slice(1));
}


// Use:

const obj = {
  props: {
    abc: {
      def: 1,
    },
    ghi: {
      jkl: 2,
    },
  },
  xyz: 3,
};

let someKnownProps = ['props.abc', 'xyz'];
let objectPaths = findObjectPaths(obj);
let hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // true

// An example of all of the paths in the object above:
someKnownProps = [
  'props',
  'props.abc',
  'props.abc.def',
  'props.ghi',
  'props.ghi.jkl',
  'xyz',
];

objectPaths = findObjectPaths(obj);
hasOtherProps = objectPaths.some(path => !someKnownProps.includes(path));
console.log(hasOtherProps); // false


// Finally, comparing the results of inherited vs non-inherited enumeration:

const objWithoutOwnProps = Object.create({
  props: {
    abc: {
      def: 1,
    },
    ghi: {
      jkl: 2,
    },
  },
  xyz: 3,
});

console.log(
  'Non-inherited props:',
  findObjectPaths(objWithoutOwnProps),
);

console.log(
  'Inherited props:',
  findObjectPaths(objWithoutOwnProps, {includeInherited: true}),
);
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
0

Similar to what Mina said:

let obj = {one: 1, two: 2, three: 3};
let knownKeys = ['one', 'two'];

for (let key in obj) {
  if (!knownKeys.includes(key)) {
    console.log(key);
  }
}
Baha
  • 388
  • 3
  • 12