How to tell where an object value is undefined?

Viewed 66

Below have I a very simple function, which I type check at compile and runtime, but I would to also rumtime check all the keys result in a value, and if it doesn't, then prompt where it broke.

The only solution I can think of is

assert(typeof db[customer] !== undefined);
assert(typeof db[customer].offers !== undefined);
assert(typeof db[customer].offers[offerId] !== undefined);
assert(typeof db[customer].offers[offerId][key] !== undefined);

but it is a lot of typing. I expected lodash would have a solution for this problem, but doesn't seem to be the case.

If I do

db?[customer]?.offers?[offerId]?[key] = value;

then I don't know where it returned undefined.

import assert from 'assert';

export function setPropertyOffer(
  db: Record<string, any>,
  customer: number,
  offerId: number,
  key: string,
  value: number | string,
) {
  assert(typeof db === 'object');
  assert(typeof customer === 'number');
  assert(typeof offerId === 'number');
  assert(typeof key === 'string');
  assert(typeof value === 'string' || typeof value === 'number');

  db[customer].offers[offerId][key] = value;
}
3 Answers

You can use a function to help you decide. It takes an object, and an array (or arguments) of keys to get value.

var obj = [{
  "category1": {
    nested: {
      a: 'string',
      b: [69, 13, 15]
    }
  },
  "category2": "2",
}];

console.log(where_undefined(obj, 0, 'category1', 'nested', 'b', 2))
// print 15

console.log(where_undefined(obj, 0, 'category1', 'nested', 'b', 4))
// print undefined at key 4

function where_undefined(obj) {
  var args = Array.prototype.slice.call(arguments)
  args.shift();
  while (args.length) {
    var arg = args.shift();
    if (obj[arg] === undefined) {
      console.log("undefined at key " + arg);
      break;
    } else {
      obj = obj[arg];
    }
  }
  return obj;
}

It is a bit complicated, but you would need to build up a structure to determine the nice name for the property in the object that is missing.

const lookup = (obj, details) => details.reduce((acc, obj) => {
  const [
    [key, value]
  ] = Object.entries(obj);
  if (acc[value] === undefined) {
    throw new Error(`Unable to get ${key}. value: ${value}`);
  }
  return acc[value];
}, obj);

const testObj1 = {
  apple: {
    offers: {
      freeCoffee: {
        black: 100,
      }
    }
  }
};

const testDBData = (customer, offerId, key) =>
([
  { customer },
  { offers: 'offers' },
  { offerId },
  { key },
]);


const result = lookup(testObj1, testDBData('apple', 'freeCoffee', 'black'));
console.log(result);

try {
  lookup(testObj1, testDBData('apple', 'freeCoffee', 'x'));
} catch(e) {
  console.error(e);
}

try {
  lookup(testObj1, testDBData('apple', 'x', 'x'));
} catch(e) {
  console.error(e);
}

try {
  lookup(testObj1, testDBData('x', 'x', 'x'));
} catch(e) {
  console.error(e);
}

Or you could do it without the array if you do not care about the name of the level.

const lookup = (obj, details) => details.reduce((acc, value) => {
  if (acc[value] === undefined) {
    throw new Error(`Unable to get ${value}`);
  }
  return acc[value];
}, obj);

const testObj1 = {
  apple: {
    offers: {
      freeCoffee: {
        black: 100,
      }
    }
  }
};


const result = lookup(testObj1, ['apple', 'offers', 'freeCoffee', 'black']);
console.log(result);

try {
  lookup(testObj1, ['apple', 'offers', 'freeCoffee', 'x4']);
} catch(e) {
  console.error(e);
}

try {
  lookup(testObj1, ['apple', 'offers', 'x3', 'x4']);
} catch(e) {
  console.error(e);
}

try {
  lookup(testObj1, ['apple', 'x2', 'x3', 'x4']);
} catch(e) {
  console.error(e);
}

try {
  lookup(testObj1, ['x1', 'x2', 'x3', 'x4']);
} catch(e) {
  console.error(e);
}

The simple answer is that you do don't or that it's irrelevant.

You get to pick the signature of your functions. If you need to reach deep down to your db argument, you are creating a dependency to the implementation details of that object.

Secondly, you get the decide what gets written to the database. If it's relevant that some property is never undefined, define it at the data schema level.

Related