Need Help with Conditional Optional Fields in OpenAI JSON Schema with strict: true

I’m using OpenAI’s response_format with type: "json_schema" and running into a problem when trying to define a field that should be optional and only included under specific conditions. When "strict": true is enabled, the model throws a 400 BadRequestError if it includes a field not listed in the "required" array, even if that field is valid in other contexts. On the other hand, if I include that field in the "required" array, it forces the model to always return it—even when it’s irrelevant. For example, I have a convo field that should only appear when a contradiction is based on a transcription, but not in all cases. If I include "convo" in "required", it appears in every object, which is undesirable. If I remove it from "required", I get an error when the model tries to return it conditionally. I also tried using constructs like oneOf, anyOf, and if-then-else to make the field conditionally required, but these are not supported by OpenAI’s schema validation. I’m looking for a clean solution or workaround to allow conditional optional fields under "strict": true mode, without triggering errors or forcing unnecessary data into every output.

A strict structured output response format forces the AI to make all the keys provided.

Answer: Create a union of the type of the field and null in the schema.

OpenAI’s own example from documentation (but the style of a function):

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": ["string", "null"],
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": [
            "location", "unit"
        ]
    }
}

Pydantic as a “structured” response format has a bad habit of making a bunch of anyOf for anything that seems optional within the “required” that is forced on the BaseModel by OpenAI’s response format input of a streamable.

Here’s a method in Python to do the "type | null" union provided in what the AI actually understands, and the API call utilizing it:

from openai import OpenAI

client = OpenAI()

from pydantic import BaseModel, Field, ConfigDict
try:
    # 3.11+
    from typing import Annotated
except ImportError:
    # 3.8–3.10
    from typing_extensions import Annotated
from pydantic.json_schema import WithJsonSchema

# Force JSON Schema to use the "type": ["string","null"] style (no anyOf)
StrOrNull = Annotated[
    str | None,
    WithJsonSchema({"type": ["string", "null"]}, mode="validation")
]

class ExtractedUserLocation(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
        json_schema_extra={
            "title": "extracted_user_location",
            "description": ("Structured output for an extracted user location.
                            Any field may be null if unknown."),
        },
    )

    # Required-but-nullable fields
    city: StrOrNull = Field(..., description="City name, or null if unknown")
    state: StrOrNull = Field(..., description="State/region/province, or null if unknown")
    postal_code: StrOrNull = Field(..., description="Postal/ZIP code, or null if unknown")


response = client.responses.parse(
    model="gpt-5-mini",
    input=[
        {"role": "system", "content": "Extract the user's location."},
        {"role": "user", "content": "I'm glad to help keep Austin weird - BBQ or Falafel."},
    ],
    text_format=ExtractedUserLocation,
)

print(response.output_parsed)

And the resulting schema, as an example of what you can send otherwise as a programming language data object or part of a REST API call:

>>> import json
>>> print(json.dumps(ExtractedUserLocation.model_json_schema(), indent=2))

{
  "additionalProperties": false,
  "description": "Structured output for an extracted user location. Any field may be null if unknown.",
  "properties": {
    "city": {
      "description": "City name, or null if unknown",
      "title": "City",
      "type": [
        "string",
        "null"
      ]
    },
    "state": {
      "description": "State/region/province, or null if unknown",
      "title": "State",
      "type": [
        "string",
        "null"
      ]
    },
    "postal_code": {
      "description": "Postal/ZIP code, or null if unknown",
      "title": "Postal Code",
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "city",
    "state",
    "postal_code"
  ],
  "title": "extracted_user_location",
  "type": "object"
}