1

I'm wondering if there is a common way, perhaps a library, to check the structure of objects (like duck typing).

This can be useful for both runtime type-checking, and for writing unit tests.

I think I'm looking for something similar to typescript "interfaces', bu typescript only does static checking.

asked Jul 19, 2019 at 19:13

3 Answers 3

4

No simple way, but how about a utility function?:

function isOfType(obj, model) {
 for (let prop in model) {
 if (!(prop in obj) || typeof obj[prop] !== typeof model[prop] || Array.isArray(model[prop]) !== Array.isArray(obj[prop])) {
 return false;
 }
 if (typeof model[prop] === 'object' && !Array.isArray(model[prop])) {
 if (!isOfType(obj[prop], model[prop])) {
 return false;
 }
 }
 }
 return true;
}

So basically, you can compare any object to a model with this. It will make sure the object has all the properties the model has, of the same types, and recursively apply that to nested objects.

answered Jul 19, 2019 at 19:45
Sign up to request clarification or add additional context in comments.

Comments

1

UPDATE: If you want duck typing as defined as https://en.wikipedia.org/wiki/Duck_typing then check-types.js https://gitlab.com/philbooth/check-types.js has you covered. See check.like(...) Additionally, based on the wiki article IceMetalPunk's solution also holds up.

Additionally, I've created a codesandbox: https://codesandbox.io/embed/optimistic-tu-d8hul?expanddevtools=1&fontsize=14&hidenavigation=1

Original answer, not exactly correct: "Going to have to give this a soft "no". There is no way to 100% know that Object A is structurally an unmodified object of class X. If you are less strict then the answer would be a soft "yes". If you are looking to compare A to B then you can compare props. Again, though you could have a case where both A and B come from the same parent class X and have not been mutated by any outside force other than calling the objects own function."

Borrowing a function from MDN to get started.

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
function listAllProperties(o) {
 var objectToInspect;
 var result = [];
 for(objectToInspect = o; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)) {
 result = result.concat(Object.getOwnPropertyNames(objectToInspect));
 }
 return result;
}

This function will check if our Object A is effectively the same as our base object created from class X.

function isPureObject(baseObj, objToCheck) {
 // instanceof to make sure we don't have a object that has the same starting definition but is actually different class
 if (!(objToCheck instanceof baseObj.constructor)) return false
 let baseProps = listAllProperties(baseObj)
 return listAllProperties(objToCheck).every(prop => baseProps.indexOf(prop) > -1)
}

Now let's create a couple of test classes.

class Test {
 constructor(b) { this.b = b }
 a() { this.d = 18}
 c = 5
}
// this is effective the same as Test but is a different class
class LikeTest {
 constructor(b) { this.b = b }
 a() { this.d = 18 }
 c = 5
}

Create some new test objects

let a = new Test(3)
let b = new Test(42)
let c = new Test(42)
let likeTest = new LikeTest(3)
c.z = 10
let base = new Test(0)

For our first set of test, we will show that our function "isPureObject" can correctly test that A is an object of class X and has not been mutated outside the starting template. I've also included IceMetalPunk's function isOfType and check.like for comparison.

Test basic cases where the test object "is" a duck that has not been mutated.

console.log(`Test basic cases where the test object "is" a duck that has not been mutated.`);
console.log(`------------------------------------------------------------`);
console.log(`expect true - isPureObject(base, a) = ${isPureObject(base, a)}`);
console.log(`expect true - isOfType(a, base) = ${isOfType(a, base)}`);
console.log(`expect true - check.like(a, base) = ${check.like(a, base)}`);
console.log(`expect true - isPureObject(base, b) = ${isPureObject(base, b)}`);
console.log(`expect true - isOfType(b, base) = ${isOfType(b, base)}`);
console.log(`expect true - check.like(b, base) = ${check.like(b, base)}`);

Test cases where the test object "is" a mutated duck.

console.log(`\n\nTest cases where the test object "is" a mutated duck.`);
console.log(`------------------------------------------------------------`);
console.log(`expect false - isPureObject(base, c) = ${isPureObject(base, c)}`);
console.log(`expect true - isOfType(c, base) = ${isOfType(c, base)}`);
console.log(`expect true - check.like(c, base) = ${check.like(c, base)}`);

Test cases where the test object "is like" a duck but not a duck.

console.log(`\n\nTest cases where the test object "is like" a duck but not a duck.`);
console.log(`------------------------------------------------------------`);
console.log(`expect false - isPureObject(base, likeTest) = ${isPureObject(base,likeTest)}`);
console.log(`expect true - isOfType(likeTest, base) = ${isOfType(likeTest, base)}`);
console.log(`expect true - check.like(likeTest, base) = ${check.like(likeTest, base)}`);

And lastly, we show why this is such a hard problem by having the object under test mutate in an intended way and make isPureObject function fail.

a.a();
console.log('\n\nCalled a.a() which sets this.d to a value that was not previously defined.')
console.log(`------------------------------------------------------------`);
console.log(`expect true - isPureObject(base, a) after calling a.a() = ${isPureObject(base, a)}`)
console.log(`expect true - isOfType(a, base) after calling a.a() = ${isOfType(a, base)}`)
console.log(`expect true - check.like(a, base) after calling a.a() = ${check.like(a, base)}`)

Original answer: "Again, I can't give this a hard no or a hard yes as I suspect there are ways this could be done using an object.constructor.toSting() to compare against the objects current state but even that may not be enough. I also know that React.js does something along these lines but they may be doing it against very specific objects/class whereas I assume you are looking for a broad general uses case."

Updated: It really depends on what you want to do. If you are looking for duck typing then there are many solutions. A few have been covered here. If you are looking for a structural/unmutated object then isPureObject will handle that. However, it will fall short on objects that can self-mutate.

answered Jul 19, 2019 at 22:23

Comments

1

Even with Typescript interfaces, there isn't an easy comparison to make sure the structure of a object matches type. For sanity checking, I've used a conditional operator to check all needed properties of the object:

yourObject = {
 name: 'cw';
 work: {
 employed: true;
 company: 'stackoverflow'
 }
}
if (yourObject &&
 yourObject.hasOwnProperty('name') &&
 yourObject.hasOwnProperty('work') &&
 yourObject.work.hasOwnProperty('employed') &&
 yourObject.work.hasOwnProperty('company')
) {
 //structure === good
}
meyi
8,2621 gold badge17 silver badges29 bronze badges
answered Jul 19, 2019 at 19:27

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.