New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple array items type validation with oneOf, anyOf #134

Closed
mgendre opened this Issue Mar 1, 2016 · 11 comments

Comments

Projects
None yet
5 participants
@mgendre

mgendre commented Mar 1, 2016

Hi everyone,

I've encountered a problem to validate multiple items type in an array.

We have an array with 2 items types. We try to validate each item to 2 different schemas types.

Here is the schema:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "transfer": {
      "type": "object",
      "properties": {
        "modes": {
          "type": "array",
          "minItems": 1,
          "additionalItems": false,
          "items": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "mode": {
                    "enum": [
                      "ftp"
                    ]
                  },
                  "account": {
                    "type": "string",
                    "pattern": "^[a-f|0-9]{8}$"
                  }
                },
                "required": [
                  "mode",
                  "account"
                ],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "mode": {
                    "enum": [
                      "email"
                    ]
                  },
                  "mailingList": {
                    "type": "string",
                    "pattern": "^[a-f|0-9]{8}$"
                  }
                },
                "required": [
                  "mode",
                  "mailingList"
                ],
                "additionalProperties": false
              }
            ]
          }
        }
      },
      "required": [
        "modes"
      ],
      "additionalProperties": false
    }
  },
  "additionalProperties": false,
  "required": [
    "transfer"
  ]
}

And the data:

{
  "transfer": {
    "modes": [
      {
        "mode": "ftp",
        "account": "e69b3f54"
      },
      {
        "mode": "email",
        "mailingList": "c3d12752"
      }
    ]
  }
}

I tried to validate the data against the schema to different online tools:
http://json-schema-validator.herokuapp.com/index.jsp
http://jsonschemalint.com/draft4/#

All these tools validate successfully my data

It's like the validator try to match the 1st item to the 1st schema and the second item to the second schema. Order should be not important.

I use version 3.8.1.

Could you help me ?

Thank you in advance

@mgendre

This comment has been minimized.

mgendre commented Mar 1, 2016

When I remove the configuration "additionalProperties": false from the type definition on "oneOf" the problem disappear.

@epoberezkin

This comment has been minimized.

Owner

epoberezkin commented Mar 1, 2016

What are your ajv options? I suspect you may be using removeAdditional option. Without it it validates ok: https://tonicdev.com/esp/56d57c034b05fd0c00e8978b

If that is the case this issue is essentially a duplicate of #129 and it is an expected behaviour according to the JSON-schema standard.

@epoberezkin

This comment has been minimized.

Owner

epoberezkin commented Mar 1, 2016

I would suggest refactoring the schema to take the duplicated keywords and all property definitions apart from required out of oneOf:

"items": {
  "type": "object",
  "properties": {
    "mode": {
      "enum": [
        "ftp"
      ]
    },
    "account": {
      "type": "string",
      "pattern": "^[a-f|0-9]{8}$"
    },
    "mailingList": {
      "type": "string",
      "pattern": "^[a-f|0-9]{8}$"
    }
  },
  "additionalProperties": false,
  "oneOf": [
    { "required": [ "mode", "account" ] },
    { "required": [ "mode", "mailingList" ] }
  ]
}

This way it will work with removeAdditional option the way you expect it to.

The previous schema works correctly, but not how you expect probably

@epoberezkin epoberezkin added the thinking label Mar 1, 2016

@mgendre

This comment has been minimized.

mgendre commented Mar 1, 2016

You are right @epoberezkin !

AJV is configured with removeAdditional -> true. The schema has been refactored to avoid multiple definitions inside oneOf, like you suggested and it's working.

Thank you @epoberezkin for your support !

@epoberezkin

This comment has been minimized.

Owner

epoberezkin commented Mar 1, 2016

You're welcome. I'll add a note in readme about it :)

@ribaptista

This comment has been minimized.

ribaptista commented Jun 22, 2016

I noticed the original schema would not validate an object containing both account and mailingList properties (that's expected), while the later refactored schema does.
Is there any workaround for this?

@epoberezkin

This comment has been minimized.

Owner

epoberezkin commented Jun 23, 2016

You can use { "not": { "required": ["account"] } } to explicitly prohibit property account.

@epoberezkin epoberezkin added the usage label Aug 11, 2016

@ngryman

This comment has been minimized.

ngryman commented Aug 20, 2016

@epoberezkin Sorry to revive this but what if you do need to reference external schemas in oneOf and can't really split them like your proposed solution?

To take the above example with ref:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "transfer": {
      "type": "object",
      "properties": {
        "modes": {
          "type": "array",
          "minItems": 1,
          "additionalItems": false,
          "items": {
            "oneOf": [
              { "$ref": "ftp" },
              { "$ref": "email" }
            ]
          }
        }
      },
      "required": [
        "modes"
      ],
      "additionalProperties": false
    }
  },
  "additionalProperties": false,
  "required": [
    "transfer"
  ]
}

Thanks!

@epoberezkin

This comment has been minimized.

Owner

epoberezkin commented Aug 28, 2016

Btw, "additionalItems": false does nothing unless the schema in items keyword is an array.

The solutions to additionalProperties limitations are:

  • don't worry about additionalProperties and simply allow them. I think the obsession with removing them is not always justified, in many cases you make your life MUCH easier by allowing them (especially when you have multiple applications using the same data/schema). The argument against it though is that by allowing additional properties you may miss some typos in property names.
  • refactor schemas as suggested above
  • use custom switch keyword (or if/then/else) instead of anyOf - it allows to control what is validated based on some conditions (so only correct properties will be treated as additional)
  • use $merge/$patch keywords to extend schemas rather than joining them with compound keywords such as anyOf etc.

Please see FAQ that points to other related issues that show these approaches to using additionalProperties.

@dnvyrn

This comment has been minimized.

dnvyrn commented Mar 9, 2018

schema:

"items": {
  "type": "object",
  "properties": {
    "mode": { "enum": [  "ftp" ]
    },
    "account": { "type": "string" },
    "mailingList": { "type": "string" },
    "otherProp": { "type": "string" }
  },
  "additionalProperties": false,
  "oneOf": [
    { "required": [ "mode", "account" ] },
    { "required": [ "mode", "mailingList", "otherProp" ] }
  ]
}

data:

{
  "transfer": {
    "modes": [
      {
        "mode": "ftp",
        "account": "e69b3f54",
        "mailingList": "c3d12752",
      },
      {
        "mode": "email",
        "mailingList": "c3d12752",
        "otherProp": "fasfasfaf"
      }
    ]
  }
}

It will validate pass. How to use { "not": { "required": ["account"] } } ?

@epoberezkin

This comment has been minimized.

Owner

epoberezkin commented Mar 9, 2018

I don’t understand the question - what are you trying to achieve? “not” can be a sibling to required and any other keyword, if that’s what you are asking.

Also it as a general JSON schema question - it should be posted to Stack Overflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment