json-validator.de

Ratgeber · JSON Schema 2020-12

Konditionelle Validation in JSON Schema: if/then/else verständlich erklärt

if/then/else seit Draft 07. Klassischer Use-Case: tagged unions. dependentSchemas als alternative Schreibweise für einfache Pflichtfeld-Logik.

Foto von Mateusz Viola

Von Mateusz Viola

Betreiber & redaktionelle Verantwortung json-validator.de

Konditionelle Validation: Wann es sinnvoll ist

Klassischer Use-Case: Tagged Unions / Discriminated Unions. Ein Objekt mit einem Typ-Feld, wo abhängig vom Typ andere Pflichtfelder gelten.

// Free-Plan braucht nur user_id
{ "plan": "free", "user_id": "usr_abc" }

// Pro-Plan braucht zusätzlich billing_email
{ "plan": "pro", "user_id": "usr_def",
  "billing_email": "billing@example.com" }

if/then/else (seit Draft 07)

{
  "type": "object",
  "required": ["plan", "user_id"],
  "properties": {
    "plan": { "enum": ["free", "pro"] },
    "user_id": { "type": "string" },
    "billing_email": { "type": "string", "format": "email" }
  },
  "if":   { "properties": { "plan": { "const": "pro" } } },
  "then": { "required": ["billing_email"] }
}

Bedeutung: wenn das if-Schema matcht (plan = pro), dann muss auch das then-Schema matchen (billing_email Pflicht). Sonst (else, falls definiert) muss das else-Schema matchen.

Mehrere Bedingungen kombinieren

Du kannst nicht mehrere if/then/else direkt nebeneinander setzen - nur eines pro Schema-Ebene. Workaround: allOf mit mehreren Sub-Schemas:

{
  "type": "object",
  "required": ["plan"],
  "properties": {
    "plan": { "enum": ["free", "pro", "enterprise"] }
  },
  "allOf": [
    {
      "if":   { "properties": { "plan": { "const": "pro" } } },
      "then": { "required": ["billing_email"] }
    },
    {
      "if":   { "properties": { "plan": { "const": "enterprise" } } },
      "then": { "required": ["billing_email", "contract_id"] }
    }
  ]
}

dependentSchemas - die andere Schreibweise

Für einfache "wenn Feld X gesetzt, dann gelten zusätzliche Constraints"-Patterns gibt es dependentSchemas:

{
  "type": "object",
  "properties": {
    "credit_card": { "type": "string" },
    "billing_address": { "type": "string" }
  },
  "dependentSchemas": {
    "credit_card": {
      "required": ["billing_address"]
    }
  }
}

Bedeutung: wenn credit_card im Objekt vorhanden ist, dann muss auch billing_address vorhanden sein.

dependentRequired - noch knapper

Wenn die Bedingung nur Pflichtfelder hinzufügt:

{
  "type": "object",
  "dependentRequired": {
    "credit_card": ["billing_address", "cvv"]
  }
}

if-Schema: Was es wirklich tut

WICHTIG: Das if-Schema wird nur als Bedingung evaluiert - Fehler im if-Schema produzieren KEINE Validation-Errors. Sie schalten nur zwischen then und else um.

Häufiger Fehler: man setzt im if-Schema "required": [...] und ist überrascht, dass fehlende Pflichtfelder nicht gemeldet werden. Pflichtfelder gehören ins then-Schema oder ins Top-Level-Schema.

Performance-Aspekt

Mehrere if/then/else-Blöcke per allOf sind etwas langsamer als ein einzelnes oneOf mit Discriminator-Optimierung. In Ajv ist der Unterschied messbar aber selten relevant (Mikrosekunden). Code-Lesbarkeit überwiegt typisch.

Alternative: oneOf mit const-Discriminator

{
  "oneOf": [
    {
      "properties": {
        "plan": { "const": "free" },
        "user_id": { "type": "string" }
      },
      "required": ["plan", "user_id"]
    },
    {
      "properties": {
        "plan": { "const": "pro" },
        "user_id": { "type": "string" },
        "billing_email": { "type": "string", "format": "email" }
      },
      "required": ["plan", "user_id", "billing_email"]
    }
  ]
}

Funktioniert auch - aber Properties stehen pro Variante neu. Bei wachsender Schema-Komplexität wird if/then/else lesbarer.

Im Tool oben testen

Setze ein Test-Schema mit if/then/else ein und variiere das Payload. Du siehst sofort welche Constraints wann aktiv werden.

Mehr zum Thema