我能否使用ajv json schema验证日期,而无需将日期转换为字符串?

23

我有一个包含一个或多个类型为日期的属性的对象。我想使用ajv json schema验证器包来验证该对象。 我可以通过使用toISOString()将类型为日期的属性转换为字符串。但是,该对象可能非常大,因此我不想转换整个对象的所有日期属性。 除将日期转换为字符串外,还有其他解决方案吗? 我是否可以创建自定义ajv模式验证器?

 // My example schema
const schema = {
  "properties": {
    "createdAt": { 
       "type": "string",
       "format": "date-time"
    },
       "lastName": { "type": "string" },
       "firstName": { "type": "string" }
  }
};

// My example testobject
const testObj = {
   createdAt: new Date(),
   lastName: "Doe",
   firstName: "John"
}

// The validation
const validate = ajv.compile(schema);
const valid = validate(testObj);
if(!valid) console.log('Invalid: ' + ajv.errorsText(validate.errors));

这将执行一个控制台日志,因为testObj.createdAt是一个日期而不是字符串。


看一下这个插件 https://github.com/epoberezkin/ajv-keywords,我认为它与你所寻找的相关。 - leocoder
1
只需将您的ajv模式从"type":"string"更改为"type":"object",内置的ajv "date-time"格式就可以工作了。在ajv版本6.10.2中进行了测试。 - Ananda Masri
2
因为Date对象比日期/时间字符串更大(且不太便携),所以我实际上建议将您的Date对象转换为字符串 - 特别是如果您计划将经过验证的数据发送到后端服务器进行重新验证。并非所有后端服务器平台都可以轻松验证javascript Date对象。 - Ananda Masri
1
@leocoder,你建议的包将如何解决这个问题?ajv包已经允许定义自定义ajv验证器。 - Ananda Masri
4个回答

10

只需将您的ajv模式从"type": "string"更改为"type": "object",内置的ajv date-time格式就会起作用。这是一个可工作的演示代码

您还可以定义一个自定义ajv格式来验证Date对象(或字符串),例如:

ajv = new Ajv();

ajv.addFormat('custom-date-time', function(dateTimeString) {
  if (typeof dateTimeString === 'object') {
    dateTimeString = dateTimeString.toISOString();
  }

  return !isNaN(Date.parse(dateTimeString));  // any test that returns true/false 
});

...并在ajv模式中调用您的自定义格式,如下所示:

const schema = {
  "properties": {
    "createdAt": {
      "type": "object",
      "format": "custom-date-time"

将所有内容整合在一起,以下是创建自定义日期格式的代码和可工作的Fiddle示例

// My example schema

const schema = {
  "properties": {
    "createdAt": {
      "type": "object",
      "format": "custom-date-time"
    },
    "lastName": {
      "type": "string"
    },
    "firstName": {
      "type": "string"
    },
    "required": [ 'createdAt', 'lastName', 'firstName' ],
    "additionalProperties": false,
  }
};

// My example testobject
const testObj = {
  createdAt: new Date(),
  lastName: "Doe",
  firstName: "John"
}


// The validation
ajv = new Ajv();

ajv.addFormat('custom-date-time', function(dateTimeString) {
  if (typeof dateTimeString === 'object') {
    dateTimeString = dateTimeString.toISOString();
  }

  return !isNaN(Date.parse(dateTimeString));  // return true/false 
});

const validate = ajv.compile(schema);
const valid = validate(testObj);

if (valid)
  alert('valid');
else
  alert('Invalid: ' + ajv.errorsText(validate.errors));

5
测试结果为 {"date": 1},因此不是有效的答案。 - Polv
@Polv,OP问道:“我能否使用ajv json schema验证日期,而无需将日期转换为字符串?” 我已更新我的答案以满足您的特定验证要求,包括在验证模式中添加“required”和“additionalProperties”。 - Ananda Masri
这是升级到Objectj 3.x.x的一个活跃问题。许多Github问题的答案强制您将值存储为字符串而不是日期。谢谢。 - jboxxx
1
@AnandaMasri 我尝试过这个方法,但效果并不如预期。当"type"为"object"时,AJV会忽略format。你可以通过将测试对象日期更改为new Date('rubbish')来在你的fiddle中尝试一下;它错误地标记了这个日期为有效。我成功地添加了一个关键字来完成这项工作。将在答案中发布。 - Andrew Eddie

1

看起来你可以通过使用 instanceof 关键字(ajv-keywords 的一部分)来实现预期的结果:

const Ajv = require("ajv");
const addKeywords = require("ajv-keywords");

const ajv = new Ajv(); // options can be passed, e.g. {allErrors: true}
addKeywords(ajv);

// My example schema
const schema = {
  type: "object",
  properties: {
    createdAt: {
      instanceof: "Date",
    },
    lastName: { type: "string" },
    firstName: { type: "string" },
  },
};

// My example testobject
const testObj = {
  createdAt: new Date(),
  lastName: "Doe",
  firstName: "John",
};

// The validation
const validate = ajv.compile(schema);
const valid = validate(testObj);
if (!valid) console.log("Invalid: " + ajv.errorsText(validate.errors));

0

不幸的是,format 属性不适用于 object 类型,因此自定义格式不是一个选项。然而,我能够添加一个自定义关键字(受到查看 instance 关键字的启发,它实际上让你走了一半的路),这给了我想要的结果(即值必须是一个日期对象,且日期必须有效)。

const { equal } = require('assert');
const Ajv = require('ajv');
const { _ } = require('ajv');
const ajv = new Ajv();
const schema = {
  type: 'object',
  properties: {
    jsdate: {
      type: 'object',
      isDate: true,
    },
  },
};

ajv.addKeyword({
  keyword: 'isDate',
  type: 'object',
  code(ctx) {
    const { data } = ctx;
    ctx.pass(_`${data} instanceof Date && !isNaN(+${data})`);
  },
});

const validate = ajv.compile(schema);

equal(validate({ jsdate: new Date() }), true, 'should succeed');

equal(validate({ jsdate: {} }), false, 'should fail for random object');

equal(
  validate({ jsdate: '2001-01-01' }),
  false,
  'should fail for valid date string'
);

equal(
  validate({ jsdate: new Date('rubbish') }),
  false,
  'should fail if Date is invalid'
);

0

其中一种有效的方式是先将其转换为符合 JSON Schema 格式的对象。

function makeJsonSchemaCompatible (obj) {
  if (Array.isArray(obj)) {
    return obj.map((subObj) => makeJsonSchemaCompatible(subObj))
  } else if (obj && typeof obj === 'object') {
    const replacement = {}
    const className = obj.constructor.name
    if (className !== 'Object') {
      replacement.__className = className
    }
    Object.entries(obj).map(([k, v]) => { replacement[k] = makeJsonSchemaCompatible(v) })
    return replacement
  }

  return obj
}

这个应该怎么工作?据我所知,makeJsonSchemaCompatible(new Date()) 只返回 { __className: "Date" },也就是说,所有 Date 的实际数据都丢失了。此外,JSON.parse(JSON.stringify(new Date())) 不能正确地往返传输。类型从 Date 变成了字符串。 - bluenote10
这个想法是有恢复器和替换器的;但我需要修复我的代码。 - Polv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接