mafintosh/is-my-json-valid

invalid element is not detected in array

pabigot opened this issue · 2 comments

The following reduced example provides an outer schema for an array containing zero or one elements matching an inner schema.

var j = require('is-my-json-valid');

var inner = {
  "type": "object",
  "definitions": {
    "E": {"enum": ["tag"]},
    "F": {"type": "boolean"},
    "E|F": {
      "oneOf": [{"$ref": "#/definitions/E"},
                {"$ref": "#/definitions/F"}]
    }
  },
  "properties": {
    "v": {"$ref": "#/definitions/E|F"}
  },
  "additionalProperties": false
};

var outer = {
  "type": "array",
  "items": [inner],
  "minItems": 0,
  "maxItems": 1
};

var iVal = j(inner, {verbose: true});
var oVal = j(outer, {verbose: true});

var doc;
doc = {v: 'tag'}; // valid inner
doc = {v: 'tug'}; // invalid inner

var rv = iVal(doc);
console.log('inner', rv, iVal.errors);
rv = oVal([doc]);
console.log('outer', rv, oVal.errors);

When run it produces this output:

inner false [ { field: 'data.v',
    message: 'referenced schema does not match',
    value: 'tug',
    type: undefined } ]
outer true null

In short, a document that is invalid as the inner schema is not detected as invalid when used as an element in the outer schema.

This fails because nesting the inner schema breaks the ref.

In the second schema, the definition is not at #/definitions/E|F but at #/items/0/definitions/E|F`. If you use that ref, you'll see the outer schema fail and the inner one pass as expected.

To make both work, you need to pull out the definitions and include them at the top level in both schemas:

var j = require('is-my-json-valid');

var definitions = {
  "E": {"enum": ["tag"]},
  "F": {"type": "boolean"},
  "E|F": {
    "oneOf": [{"$ref": "#/definitions/E"},
              {"$ref": "#/definitions/F"}]
  }
};

var inner = {
  "type": "object",
  definitions,
  "properties": {
    "v": {"$ref": "#/definitions/E|F"}
  },
  "additionalProperties": false
};

var outer = {
  "type": "array",
  definitions,
  "items": [inner],
  "minItems": 0,
  "maxItems": 1
};

var iVal = j(inner, {verbose: true});
var oVal = j(outer, {verbose: true});

var doc;
doc = {v: 'tag'}; // valid inner
doc = {v: 'tug'}; // invalid inner

var rv = iVal(doc);
console.log('inner', rv, iVal.errors);
rv = oVal([doc]);
console.log('outer', rv, oVal.errors);

This fails as expected in both the inner and outer case:

inner false [ { field: 'data.v',
    message: 'referenced schema does not match',
    value: 'tug',
    type: undefined } ]
outer false [ { field: 'data["0"].v',
    message: 'referenced schema does not match',
    value: 'tug',
    type: undefined } ]

That makes sense. Thanks.