如何使用 Joi 表达式从相对 属性 键访问全局对象 属性

How to access global object property from relative property key using Joi expression

我正在尝试根据在 validate 处传递的全局上下文和正在验证的对象的现有 属性 设置默认值 属性。我似乎无法使用对象的 属性 的值作为全局上下文的键。

const context = {
    letters: {
        a: 1,
        b: 2,
        c: 3,
    },
};

const s = Joi.object().keys({
    letter: Joi.string().valid(...['a', 'b', 'c']),
    num: Joi.number().default(Joi.expression('{$letters}.{letter}'))
})

const test = {
    letter: 'a',
}

console.log(s.validate(test, { context }));

上面的简单示例。我有一个名为 letters 的对象,我将其放入上下文中。该架构将查找 letter,然后尝试使用全局字母作为要从中提取的对象并将传入的字母作为键来为 num 设置默认值。我能得到的最接近的是 { value: { letter: 'a', num: '[object Object].a' } }

default方法可以接受

  • a function which returns the default value using the signature function(parent, helpers) where:
    • parent - a clone of the object containing the value being validated. Note that since specifying a parent argument performs cloning, do not declare format arguments if you are not using them.
    • helpers - same as those described in any.custom().

helpers 包括包含当前首选项的 属性 prefs,包括具有当前上下文的 context 属性。

您可以使用它来提供基于上下文和其他属性的自定义默认值:

const Joi = joi; // exported as joi in the browser bundle

const context = {
    letters: {
        a: 1,
        b: 2,
        c: 3,
    },
};

const s = Joi.object().keys({
    letter: Joi.string().valid(...["a", "b", "c"]),
    num: Joi
        .number()
        .default(({ letter }, { prefs: { context } }) => context.letters[letter]),
});

const test = {
    letter: "a",
};

console.log(s.validate(test, { context }));
console.log(s.validate({ letter: "b" }, { context }));
console.log(s.validate({ letter: "a", num: 4 }, { context }));
<script src="https://cdn.jsdelivr.net/npm/joi@17.4.2/dist/joi-browser.min.js"></script>

以下一些可行的示例

Joi.expression(`{$letters[.a]}`)
Joi.expression(`{{$letters.b}}`)
Joi.expression(`{{$letters[.c]}}`)

但这里的问题是我们不能使用像 letter 这样的变量来代替 a 或 b 或 c。

但是我们可以通过自定义函数实现所需要的

// 
Object.byString = function(o, s) {
  s = s.replace(/\[(\w+)\]/g, '.'); // convert indexes to properties
  s = s.replace(/^\./, ''); // strip a leading dot
  var a = s.split('.');
  for (var i = 0, n = a.length; i < n; ++i) {
    var k = a[i];
    if (k in o) {
      o = o[k];
    } else {
      return;
    }
  }
  return o;
};

function customEvaluator(expression) {
  return function(parent, helpers) {
    let global = expression[0] == '$';
    let exp = '';
    for (var i = global ? 1 : 0, n = expression.length; i < n; ++i) {
      if (expression[i] == '#') {
        let part = expression.slice(i + 1);
        let end = -1;
        [' ', '.', '[', ']', '{', '}'].forEach(i => {
          let tmp = part.indexOf(i);
          if (end == -1 && tmp != -1) {
            end = tmp;
          }
        });

        end = end == -1 ? part.length : end;
        exp += parent[part.slice(0, end)];
        i += end;
      } else {
        exp += expression[i];
      }
    }

    return Object.byString(global ? helpers.prefs.context : parent, exp);
  };
}

const Joi = joi; // exported as joi in the browser bundle

const s = Joi.object().keys({
    letter: Joi.string().valid(...["a", "b", "c"]),
    num: Joi
        .number()
        .default(customEvaluator('$letters.#letter')),
});

const context = {
    letters: {
        a: 1,
        b: 2,
        c: 3,
    },
};

console.log(s.validate({ letter: "a" }, { context }));
console.log(s.validate({ letter: "b" }, { context }));
console.log(s.validate({ letter: "a", num: 4 }, { context }));
<script src="https://cdn.jsdelivr.net/npm/joi@17.4.2/dist/joi-browser.min.js"></script>