Skip to content

Generating Structured Data

This page adapts the original AI SDK documentation: Generating Structured Data.

While text generation can be useful, your use case will likely call for generating structured data. For example, you might want to extract information from text, classify data, or generate synthetic data.

Many language models are capable of generating structured data, often defined as using “JSON modes” or “tools”. However, you need to manually provide schemas and then validate the generated data as LLMs can produce incorrect or incomplete structured data.

The AI SDK standardises structured object generation across model providers with the generateObject and streamObject functions. You can use both functions with different output strategies, e.g. array, object, enum, or no-schema, and with different generation modes, e.g. auto, tool, or json. You can describe the shape using JSON Schema, and the AI model will generate data that conforms to that structure.

Note In the Swift port, the most ergonomic option is generateObject(schema: MyCodableType.self). For advanced scenarios you can still fall back to FlexibleSchema<MyType>.jsonSchema(...) or Schema.codable(...).

The generateObject generates structured data from a prompt. The schema is also used to validate the generated data, ensuring type safety and correctness.

import SwiftAISDK
import OpenAIProvider
struct Ingredient: Codable, Sendable {
let name: String
let amount: String
}
struct Recipe: Codable, Sendable {
let name: String
let ingredients: [Ingredient]
let steps: [String]
}
let result = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe.",
schemaName: "recipe",
schemaDescription: "A recipe for lasagna."
).object
print(result.name)
print(result.ingredients)

Note See generateObject usage examples below.

Sometimes you need access to the full response from the model provider, e.g. to access some provider-specific headers or body content.

You can access the raw response headers and body using the response property:

let result = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe.",
schemaName: "recipe",
schemaDescription: "A recipe for lasagna."
)
print(String(describing: result.response.headers))
print(String(describing: result.response.body))

When you need the full payload (same shape as JSON.stringify in TypeScript), call jsonString() or jsonValue() on the result.

let fullResult = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe."
)
let json = try fullResult.jsonString()
print(json)

Some projects maintain JSON Schema definitions separately (for example in TypeScript or via an OpenAPI registry). Swift exposes a helper that mirrors the TypeScript DX:

let recipeSchemaJSON: JSONValue = [
"type": "object",
"properties": [
"recipe": [
"type": "object",
"properties": [
"name": ["type": "string"],
"ingredients": [
"type": "array",
"items": ["type": "object"]
]
]
]
]
]
let result = try await generateObject(
model: openai("gpt-4o"),
schema: FlexibleSchema<Response>.jsonSchema(recipeSchemaJSON),
prompt: "Generate a lasagna recipe.",
mode: .json
)

FlexibleSchema<Response>.jsonSchema wraps Schema.codable internally, so developers can copy the JSON Schema from the TypeScript docs and still receive a strongly typed Response.

Given the added complexity of returning structured data, model response time can be unacceptable for your interactive use case. With the streamObject function, you can stream the model’s response as it is generated.

import SwiftAISDK
import OpenAIProvider
let stream = try streamObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe.",
schemaName: "recipe",
schemaDescription: "A recipe for lasagna."
)
for try await partial in stream.partialObjectStream {
print(partial)
}

You can use streamObject to stream generated UIs in combination with React Server Components (see Generative UI)) or the useObject hook.

Note See streamObject usage in Swift examples below.

streamObject immediately starts streaming. Errors become part of the stream and are not thrown to prevent e.g. servers from crashing.

To log errors, you can provide an onError callback that is triggered when an error occurs.

let _ = try streamObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe.",
schemaName: "recipe",
schemaDescription: "A recipe for lasagna.",
onError: { error in
print("Stream error: \(error)")
}
)

You can use both functions with different output strategies, e.g. array, object, enum, or no-schema.

The default output strategy is object, which returns the generated data as an object. You don’t need to specify the output strategy if you want to use the default.

If you want to generate an array of objects, you can set the output strategy to array. When you use the array output strategy, the schema specifies the shape of an array element. With streamObject, you can also stream the generated array elements using elementStream.

struct Hero: Codable, Sendable {
let name: String
let `class`: String
let description: String
}
let arrayStream = try streamObject(
model: openai("gpt-4o"),
output: .array(schema: FlexibleSchema.auto(Hero.self)),
prompt: "Generate 3 hero descriptions for a fantasy role playing game.",
schemaName: "hero",
schemaDescription: "A hero character for a fantasy RPG."
)
for try await hero in arrayStream.elementStream {
print(hero)
}

If you want to generate a specific enum value, e.g. for classification tasks, you can set the output strategy to enum and provide a list of possible values in the enum parameter.

Note Enum output is only available with generateObject.

let genres = ["action","comedy","drama","horror","sci-fi"]
let enumResult = try await generateObject(
model: openai("gpt-4o"),
output: .enum(cases: genres),
prompt: "Classify the genre of this movie plot: 'A group of astronauts travel through a wormhole in search of a new habitable planet for humanity.'"
)
print(enumResult.object)

In some cases, you might not want to use a schema, for example when the data is a dynamic user request. You can use the output setting to set the output format to no-schema in those cases and omit the schema parameter.

let noSchema = try await generateObject(
model: openai("gpt-4o"),
output: .noSchema,
prompt: "Generate a lasagna recipe."
)
print(noSchema.object)

You can optionally specify a name and description for the schema. These are used by some providers for additional LLM guidance, e.g. via tool or schema name.

let named = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe.",
schemaName: "recipe",
schemaDescription: "A recipe for a dish."
)
print(named.object)

You can access the reasoning used by the language model to generate the object via the reasoning property on the result. This property contains a string with the model’s thought process, if available.

let reasoning = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "Generate a lasagna recipe.",
schemaName: "recipe",
schemaDescription: "A recipe for lasagna.",
providerOptions: [
"openai": [
"strictJsonSchema": .bool(true),
"reasoningSummary": .string("detailed")
]
]
)
print(reasoning.reasoning)

When generateObject cannot generate a valid object, it throws a NoObjectGeneratedError.

This error occurs when the AI provider fails to generate a parsable object that conforms to the schema. It can arise due to the following reasons:

  • The model failed to generate a response.
  • The model generated a response that could not be parsed.
  • The model generated a response that could not be validated against the schema.

The error preserves the following information to help you log the issue:

  • text: The text that was generated by the model. This can be the raw text or the tool call text, depending on the object generation mode.
  • response: Metadata about the language model response, including response id, timestamp, and model.
  • usage: Request token usage.
  • cause: The cause of the error (e.g. a JSON parsing error). You can use this for more detailed error handling.
import SwiftAISDK
do {
_ = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "...",
schemaName: "recipe"
)
} catch let error as NoObjectGeneratedError {
print("NoObjectGeneratedError")
print("Cause:", error.cause as Any)
print("Text:", error.text as Any)
print("Response:", error.response as Any)
print("Usage:", error.usage as Any)
}

Warning The repairText function is experimental and may change.

Sometimes the model will generate invalid or malformed JSON. You can use the repairText function to attempt to repair the JSON.

It receives the error, either a JSONParseError or a TypeValidationError, and the text that was generated by the model. You can then attempt to repair the text and return the repaired text.

let repaired = try await generateObject(
model: openai("gpt-4o"),
schema: Recipe.self,
prompt: "...",
schemaName: "recipe",
experimentalRepairText: { text, error in
return text + "}"
}
)

Structured outputs with generateText and streamText

Section titled “Structured outputs with generateText and streamText”

You can generate structured data with generateText and streamText by using the experimental_output setting.

Note Some models support structured outputs and tool calling simultaneously with generateText/streamText.

Warning Structured output generation with generateText and streamText is experimental and may change.

struct Person: Codable, Sendable {
struct Contact: Codable, Sendable {
let type: String
let value: String
}
struct Occupation: Codable, Sendable {
let type: String
let company: String
let position: String
}
let name: String
let age: Double?
let contact: Contact
let occupation: Occupation
}
let gt = try await generateText(
model: openai("gpt-4o"),
experimentalOutput: Output.object(Person.self),
prompt: "Generate an example person for testing."
)
let person = try gt.experimentalOutput
let st = try streamText(
model: openai("gpt-4o"),
experimentalOutput: Output.object(Person.self),
prompt: "Generate an example person for testing."
)
for try await partial in st.experimentalPartialOutputStream {
print(partial)
}

You can see generateObject and streamObject in action using various frameworks in the following examples:

Examples for Swift will be added in Quickstarts; Node/Next links are omitted in the Swift port.

Examples for Swift will be added in Quickstarts; Node/Next links are omitted in the Swift port.