web-dev-qa-db-fra.com

Schéma JSON: "allof" avec "additionalProperties"

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.

49
lm.

[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!

70
fge

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.

7
cloudfeet

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.

5
Ted Epstein

Ne définissez pas additionalProperties = false au niveau de la définition

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.

2
Yves M.