How to Use If-Then-Else in JSON Schema: Handling Missing or Empty Properties (Draft-07)

JSON Schema is a powerful tool for validating the structure, format, and content of JSON data. Whether you’re building APIs, validating user inputs, or ensuring data consistency in databases, JSON Schema helps enforce rules like required fields, data types, and value ranges. However, real-world data is rarely perfect: properties may be missing, or values may be empty (e.g., null, empty strings, or empty arrays).

To handle these edge cases gracefully, JSON Schema Draft-07 introduced the if-then-else conditional keywords. These keywords let you define contextual validation rules—applying different schemas based on whether a condition (defined in if) is met. In this blog, we’ll dive deep into using if-then-else to handle missing or empty properties, with practical examples and best practices.

Table of Contents#

  1. What is if-then-else in JSON Schema?
  2. Basic Syntax of if-then-else
  3. Handling Missing Properties
  4. Handling Empty Properties
  5. Combining Conditions with Logical Keywords
  6. Edge Cases and Best Practices
  7. Conclusion
  8. References

What is if-then-else in JSON Schema?#

The if-then-else keywords enable conditional validation. Here’s how they work:

  • if: Defines a schema that acts as the "condition." If the input JSON data validates against if, then...
  • then: The schema defined here is applied to the data.
  • else (optional): If the input data does NOT validate against if, this schema is applied instead.

Think of it as: “If [condition] is true, then enforce [rule A]; else, enforce [rule B].”

This is especially useful for handling missing or empty properties, where validation rules depend on the presence or value of other fields.

Basic Syntax of if-then-else#

The if-then-else keywords are added directly to your JSON Schema object. Here’s the basic structure:

{
  "type": "object",
  "properties": {
    "field1": { "type": "string" },
    "field2": { "type": "number" }
  },
  "if": {
    // Condition: e.g., "field1" is missing or empty
  },
  "then": {
    // Rule to apply if condition is met
  },
  "else": {
    // Rule to apply if condition is NOT met (optional)
  }
}

Let’s break this down with a simple example:
If field1 is present, then field2 must be greater than 10; else, field2 is optional.

{
  "type": "object",
  "properties": {
    "field1": { "type": "string" },
    "field2": { "type": "number" }
  },
  "if": {
    "required": ["field1"] // Condition: "field1" is present
  },
  "then": {
    "properties": { "field2": { "minimum": 10 } },
    "required": ["field2"] // Enforce "field2" > 10 if "field1" exists
  },
  "else": {
    "properties": { "field2": { "type": "number" } } // "field2" is optional
  }
}

Handling Missing Properties#

A "missing property" is one that does not appear in the JSON object (e.g., an object with { "name": "Alice" } is missing the email property). To check for missing properties, use the required keyword in the if condition.

Example 1: Require a Fallback Field When a Property is Missing#

Suppose you’re validating user profiles. If a user does not provide an email, they must provide a phone number.

Schema:#

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "phone": { "type": "string", "pattern": "^\\+?[0-9]{10,15}$" } // E.164 format
  },
  "required": ["name"], // "name" is always required
 
  // If "email" is MISSING...
  "if": {
    "not": { "required": ["email"] } // "not" inverts the condition: "email" is NOT required
  },
  // ...then "phone" is REQUIRED
  "then": {
    "required": ["phone"]
  }
}

Valid Instance (Missing email, has phone):#

{
  "name": "Bob",
  "phone": "+1234567890"
}

Invalid Instance (Missing email AND phone):#

{
  "name": "Bob" // Fails: "phone" is required when "email" is missing
}

Example 2: Validate Nested Missing Properties#

For nested objects, use dot notation with properties to check for missing nested fields.

Scenario: If a user’s address object is missing city, then zipcode must be present.

Schema:#

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "address": {
      "type": "object",
      "properties": {
        "city": { "type": "string" },
        "zipcode": { "type": "string", "pattern": "^\\d{5}$" }
      }
    }
  },
  "required": ["name", "address"],
 
  // If "address.city" is MISSING...
  "if": {
    "properties": {
      "address": {
        "not": { "required": ["city"] } // "address" does NOT require "city" (i.e., "city" is missing)
      }
    }
  },
  // ...then "address.zipcode" is REQUIRED
  "then": {
    "properties": {
      "address": { "required": ["zipcode"] }
    }
  }
}

Valid Instance (Missing city, has zipcode):#

{
  "name": "Alice",
  "address": { "zipcode": "90210" }
}

Handling Empty Properties#

An "empty property" is one that exists but has an empty value (e.g., "", null, [], or {}). We’ll handle common cases like empty strings, arrays, and null values.

Example 1: Empty Strings#

Scenario: If bio is an empty string (""), then short_bio (with minLength: 5) is required.

Schema:#

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "bio": { "type": "string" },
    "short_bio": { "type": "string", "minLength": 5 }
  },
  "required": ["bio"],
 
  // If "bio" is an empty string...
  "if": {
    "properties": {
      "bio": { "type": "string", "minLength": 0, "maxLength": 0 } // Empty string
    }
  },
  // ...then "short_bio" is required (and must be ≥5 chars)
  "then": {
    "required": ["short_bio"]
  }
}

Valid Instance (Empty bio, has short_bio):#

{
  "bio": "",
  "short_bio": "Loves cats" // Valid: "short_bio" ≥5 chars
}

Invalid Instance (Empty bio, missing short_bio):#

{
  "bio": "" // Fails: "short_bio" is required when "bio" is empty
}

Example 2: Empty Arrays#

Scenario: If tags is an empty array ([]), then default_tag must be "untagged".

Schema:#

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "tags": { "type": "array", "items": { "type": "string" } },
    "default_tag": { "type": "string" }
  },
  "required": ["tags"],
 
  // If "tags" is an empty array...
  "if": {
    "properties": {
      "tags": { "type": "array", "minItems": 0, "maxItems": 0 } // Empty array
    }
  },
  // ...then "default_tag" must be "untagged"
  "then": {
    "properties": {
      "default_tag": { "const": "untagged" } // "const" enforces exact value
    },
    "required": ["default_tag"]
  }
}

Valid Instance (Empty tags, default_tag: "untagged"):#

{
  "tags": [],
  "default_tag": "untagged"
}

Example 3: null Values#

Scenario: If profile_image is null, then use_gravatar must be true.

Schema:#

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "profile_image": { "type": ["string", "null"] }, // Can be string or null
    "use_gravatar": { "type": "boolean" }
  },
  "required": ["profile_image"],
 
  // If "profile_image" is null...
  "if": {
    "properties": {
      "profile_image": { "const": null } // Explicitly check for null
    }
  },
  // ...then "use_gravatar" must be true
  "then": {
    "properties": {
      "use_gravatar": { "const": true }
    },
    "required": ["use_gravatar"]
  }
}

Valid Instance (profile_image: null, use_gravatar: true):#

{
  "profile_image": null,
  "use_gravatar": true
}

Combining Conditions with Logical Keywords#

For complex scenarios, combine conditions in if using allOf (all conditions met), anyOf (any condition met), or oneOf (exactly one condition met).

Example: allOf for Multiple Conditions#

Scenario: If email is missing AND phone is an empty string, then alternate_contact is required.

Schema:#

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "email": { "type": "string" },
    "phone": { "type": "string" },
    "alternate_contact": { "type": "string" }
  },
 
  // If (email is missing) AND (phone is empty string)...
  "if": {
    "allOf": [
      { "not": { "required": ["email"] } }, // Condition 1: email missing
      { "properties": { "phone": { "type": "string", "maxLength": 0 } } } // Condition 2: phone is ""
    ]
  },
  // ...then "alternate_contact" is required
  "then": {
    "required": ["alternate_contact"]
  }
}

Valid Instance (Missing email, empty phone, has alternate_contact):#

{
  "phone": "",
  "alternate_contact": "[email protected]"
}

Example: anyOf for Optional Conditions#

Scenario: If either name is missing OR age is null, then id is required.

Schema:#

{
  "if": {
    "anyOf": [
      { "not": { "required": ["name"] } }, // Condition 1: name missing
      { "properties": { "age": { "const": null } } } // Condition 2: age is null
    ]
  },
  "then": { "required": ["id"] }
}

Edge Cases and Best Practices#

  1. else is Optional: If you only care about the "if-true" case, omit else. The schema will ignore then when if is false.
  2. Avoid Overly Complex if Conditions: Use allOf/anyOf sparingly. Break large conditions into reusable schemas with $ref for readability.
  3. Handle Non-Object Instances: If your schema allows non-object types (e.g., type: ["object", "string"]), ensure if conditions are compatible. For example, a string instance will fail an if that checks for object properties, triggering else.
  4. Test with Validators: Use tools like JSON Schema Validator to test edge cases (e.g., missing AND empty properties).

Conclusion#

The if-then-else keywords in JSON Schema Draft-07 empower you to write flexible, context-aware validation rules. By combining if with required, not, and logical keywords like allOf, you can gracefully handle missing or empty properties—ensuring your data remains consistent even when real-world inputs are imperfect.

Whether you’re validating user profiles, API payloads, or configuration files, if-then-else is a critical tool for building robust schemas.

References#