Can I validate a date using ajv json schema, without converting the date to string?
Asked Answered
O

4

25

I have an object which contains one or more properties of type date. I would like to validate the object using the ajv json schema validator package. I could convert the properties of type date to a string by using the toISOString(). But the object can be quiet big and thus I dont want to convert all the date-properties of the whole object. Is there a solution other than converting the date to a string? Could I somehow create a custom ajv schema validator?

 // 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));

This will do a console log, because the testObj.createdAt is a date and not a string.

Onia answered 4/4, 2019 at 16:42 Comment(4)
Take a look at this plugin github.com/epoberezkin/ajv-keywords is related what you are looking for I thinkCipango
Simply change your ajv schema from "type": "string" to "type": "object" and the built-in ajv "date-time" format will work. Tested in ajv version 6.10.2.Node
Because the Date object is bigger (and less portable) than a date/time string, I'd actually recommend converting your Date objects to strings - especially if you're planning to send the validated data to your back-end server for re-validation. Not all back-end server platforms would easily validate a javascript Date object.Node
@Cipango how exactly would the package you suggested solve the problem? The ajv package already allows to define custom ajv validators.Node
N
10

Simply change your ajv schema from "type": "string" to "type": "object" and the built-in ajv date-time format will work. Here's a working fiddle.

You can also define a custom ajv format to validate a Date object (or string) like this:

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 
});

... and invoke your custom format in the ajv schema like this:

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

Putting it all together here's the code and a working fiddle for creating a custom date format:

// 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));
Node answered 20/11, 2019 at 20:36 Comment(4)
Test passed with {"date": 1}, so not a valid answer.Murtha
@Polv, the OP asked "Can I validate a date using ajv json schema, without converting the date to string?". I've updated my answer to address your specific validation requirement by including "required" & "additionalProperties" in the validation schema.Node
This is an active issue with upgrading to objection 3.x.x. Many answers on github issues force to you store the value as string instead of a date. Thank youAiguillette
@AnandaMasri I tried this and it does not work quite as expected. AJV ignores format when the type is "object". You can try this in your fiddle by changing the test object date to new Date('rubbish'); it incorrectly marks this as valid. I was able to successfully add a keyword to do the job. Will post in an answer.Paronymous
S
3

It seems that you could achieve the expected result by using the instanceof keyword (part of 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));
Sisterly answered 21/1, 2023 at 14:24 Comment(0)
M
0

One of the valid way is to convert first to JSON Schema-compatible object.

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
}
Murtha answered 17/5, 2020 at 0:3 Comment(2)
How is this supposed to work? As far as I can tell makeJsonSchemaCompatible(new Date()) returns only { __className: "Date" }, i.e., all the actual data of the Date is lost. Also, JSON.parse(JSON.stringify(new Date())) does not round-trip properly. The type changes from Date to string.Whitesmith
The idea is there is reviver and replacer; but I will need to fix my code.Murtha
P
0

Unfortunately the format property does not proc' for object types, so custom formats aren't an option. However, I was able to add a custom keyword (inspired by looking at the instance keyword, which actually gets you half way there) that gave me the desired results (that being the value must be a Date object and the Date must be valid).

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'
);
Paronymous answered 25/8, 2022 at 6:11 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.