Supposons que nous ayons un schéma suivant le schéma (du tutoriel ici ):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
},
"type": "object",
"properties": {
"billing_address": { "$ref": "#/definitions/address" },
"shipping_address": {
"allOf": [
{ "$ref": "#/definitions/address" },
{ "properties":
{ "type": { "enum": [ "residential", "business" ] } },
"required": ["type"]
}
]
}
}
}
Et voici une instance valide:
{
"shipping_address": {
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC",
"type": "business"
}
}
Je dois m'assurer que tous les champs supplémentaires pour shipping_address
ne sera pas valide. Je sais que dans ce but existe additionalProperties
qui devrait être réglé sur "false". Mais quand je mets "additionalProprties":false
comme suit:
"shipping_address": {
"allOf": [
{ "$ref": "#/definitions/address" },
{ "properties":
{ "type": { "enum": [ "residential", "business" ] } },
"required": ["type"]
}
],
"additionalProperties":false
}
J'obtiens une erreur de validation (vérifié ici ):
[ {
"level" : "error",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/shipping_address"
},
"instance" : {
"pointer" : "/shipping_address"
},
"domain" : "validation",
"keyword" : "additionalProperties",
"message" : "additional properties are not allowed",
"unwanted" : [ "city", "state", "street_address", "type" ]
} ]
La question est: comment dois-je limiter les champs pour le shipping_address
partie seulement? Merci d'avance.
[auteur du projet de spécification de validation v4 ici]
Vous êtes tombé sur le problème le plus courant dans le schéma JSON, à savoir son incapacité fondamentale à faire l'héritage comme les utilisateurs l'attendent; mais en même temps, c'est l'une de ses principales caractéristiques.
Quand vous faites:
"allOf": [ { "schema1": "here" }, { "schema2": "here" } ]
schema1
et schema2
n'ont aucune connaissance les uns des autres; ils sont évalués dans leur propre contexte.
Dans votre scénario, rencontré par de très nombreuses personnes, vous vous attendez à ce que les propriétés définies dans schema1
sera connu de schema2
; mais ce n'est pas le cas et ne le sera jamais.
Ce problème est la raison pour laquelle j'ai fait ces deux propositions pour le projet v5:
Votre schéma pour shipping_address
serait alors:
{
"merge": {
"source": { "$ref": "#/definitions/address" },
"with": {
"properties": {
"type": { "enum": [ "residential", "business" ] }
}
}
}
}
ainsi que la définition de strictProperties
à true
dans address
.
Soit dit en passant, je suis également l'auteur du site Web auquel vous faites référence.
Maintenant, permettez-moi de revenir sur le projet v3. Le projet v3 définissait extends
et sa valeur était soit un schéma, soit un tableau de schémas. Par la définition de ce mot-clé, cela signifiait que l'instance devait être valide par rapport au schéma actuel et tous les schémas spécifiés dans extends
; en gros, le brouillon allOf
du brouillon v4 est le brouillon extends
dans le brouillon v3.
Considérez ceci (projet v3):
{
"extends": { "type": "null" },
"type": "string"
}
Et maintenant, ça:
{
"allOf": [ { "type": "string" }, { "type": "null" } ]
}
Ce sont les mêmes. Ou peut-être ça?
{
"anyOf": [ { "type": "string" }, { "type": "null" } ]
}
Ou ça?
{
"oneOf": [ { "type": "string" }, { "type": "null" } ]
}
Dans l'ensemble, cela signifie que extends
dans le projet v3 n'a jamais vraiment fait ce que les gens attendaient de lui. Avec le projet v4, *Of
les mots clés sont clairement définis.
Mais le problème que vous avez est de loin le problème le plus fréquemment rencontré. D'où mes propositions qui éteindraient cette source de malentendus une fois pour toutes!
additionalProperties
s'applique à toutes les propriétés qui ne sont pas prises en compte par properties
ou patternProperties
dans le schéma immédiat.
Cela signifie que lorsque vous avez:
{
"allOf": [
{ "$ref": "#/definitions/address" },
{ "properties":
{ "type": { "enum": [ "residential", "business" ] } },
"required": ["type"]
}
],
"additionalProperties":false
}
additionalProperties
s'applique ici aux propriétés all, car il n'y a pas d'entrée properties
au niveau frère - celle à l'intérieur allOf
ne compte pas.
Vous pouvez notamment déplacer la définition de properties
d'un niveau vers le haut et fournir des entrées de stub pour les propriétés que vous importez:
{
"allOf": [{"$ref": "#/definitions/address"}],
"properties": {
"type": {"enum": ["residential", "business"]},
"addressProp1": {},
"addressProp2": {},
...
},
"required": ["type"],
"additionalProperties":false
}
Cela signifie que additionalProperties
ne s'appliquera pas aux propriétés souhaitées.
Voici une version légèrement simplifiée de Solution d'Yves-M :
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
}
},
"type": "object",
"properties": {
"billing_address": {
"$ref": "#/definitions/address"
},
"shipping_address": {
"allOf": [
{
"$ref": "#/definitions/address"
}
],
"properties": {
"type": {
"enum": [
"residential",
"business"
]
},
"street_address": {},
"city": {},
"state": {}
},
"required": [
"type"
],
"additionalProperties": false
}
}
}
Cela préserve la validation des propriétés requises dans le schéma de base address
et ajoute simplement la propriété type
requise dans le shipping_address
.
Il est regrettable que additionalProperties
ne prenne en compte que les propriétés immédiates de niveau frère. Il y a peut-être une raison à cela. Mais c'est pourquoi nous devons répéter les propriétés héritées.
Ici, nous répétons les propriétés héritées sous forme simplifiée, en utilisant la syntaxe d'objet vide. Cela signifie que les propriétés portant ces noms seraient valides quel que soit le type de valeur qu'elles contiennent. Mais nous pouvons compter sur le mot clé allOf
pour appliquer les contraintes de type (et toute autre contrainte) déclarées dans le schéma de base address
.
Et tout ira bien:
{
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
}
}
},
"type": "object",
"properties": {
"billing_address": {
"allOf": [
{ "$ref": "#/definitions/address" }
],
"properties": {
"street_address": {},
"city": {},
"state": {}
},
"additionalProperties": false
"required": ["street_address", "city", "state"]
},
"shipping_address": {
"allOf": [
{ "$ref": "#/definitions/address" },
{
"properties": {
"type": {
"enum": ["residential","business"]
}
}
}
],
"properties": {
"street_address": {},
"city": {},
"state": {},
"type": {}
},
"additionalProperties": false
"required": ["street_address","city","state","type"]
}
}
}
Chacun de vos billing_address
et shipping_address
devrait spécifier leurs propres propriétés requises.
Votre définition ne doit pas avoir "additionalProperties": false
si vous souhaitez combiner ses propriétés avec d'autres.