AI models normally return natural-language text.
For a simple chatbot, that is exactly what an application needs:
User
|
v
AI Model
|
v
"Your order has been shipped."
But many applications need something more structured.
For example, an order-processing application might need:
{
"orderId": "ORD-10025",
"status": "Shipped",
"carrier": "Example Express",
"estimatedDelivery": "2026-10-06"
}
A C# application can then deserialize that JSON into an object:
OrderStatus result;
This changes the AI integration from:
AI
|
v
string
|
v
manual parsing
to:
AI
|
v
JSON
|
v
System.Text.Json
|
v
C# object
|
v
application logic
Current .NET AI APIs support both generic typed structured responses and explicit JSON response formats. Microsoft.Extensions.AI.ChatResponseFormat.Json represents structured JSON without a fixed schema, while the structured-output extensions provide GetResponseAsync<T> for schema-based typed results. (learn.microsoft.com, learn.microsoft.com)
This article focuses specifically on JSON responses from AI models using C# and .NET.
What Is a JSON Response from an AI Model?
A JSON response is AI-generated output that follows JSON syntax instead of ordinary prose.
Plain text:
The customer is unhappy with the product and the issue is related
to billing.
JSON:
{
"category": "Billing",
"sentiment": "Negative",
"summary": "Customer reports a billing issue."
}
JSON is useful because software can process it directly.
For example:
if (result.Category == "Billing")
{
await billingQueue.EnqueueAsync(...);
}
Instead of interpreting a sentence, the application works with a defined data structure.
Why Use JSON with AI?
JSON is useful whenever the AI result becomes input to another software component.
Common scenarios include:
document extraction
resume parsing
invoice processing
ticket classification
sentiment analysis
product extraction
recommendation metadata
search filters
database query parameters
API requests
workflow routing
agent decisions
RAG citations
A typical architecture is:
AI Application
|
v
Prompt
|
v
AI Model
|
v
JSON
|
v
System.Text.Json
|
v
C# Object
|
+--------------+--------------+
| | |
v v v
Database API Workflow
JSON vs Plain Text
Consider a support-ticket classifier.
Plain Text
This ticket should be classified as billing because
the customer was charged twice.
The application still has to determine:
Category = Billing
Reason = duplicate charge
JSON
{
"category": "Billing",
"reason": "Duplicate charge"
}
Now the application can directly deserialize the data.
var result =
JsonSerializer.Deserialize<TicketClassification>(
json);
JSON therefore acts as a boundary between AI generation and application logic.
JSON vs Structured Output
The concepts are related but not identical.
JSON response:
AI
|
v
JSON text
Structured output:
AI
|
v
Defined schema
|
v
JSON
|
v
C# type
A plain JSON response can have a flexible shape.
A structured response usually has a predefined contract.
For example:
JSON
{
"name": "...",
"age": 25
}
versus a schema defining:
name -> string
age -> integer
The previous topic, Structured AI Responses with .NET, covered the typed GetResponseAsync<T> abstraction. This article goes deeper into the JSON layer itself.
JSON Support in Microsoft.Extensions.AI
The current Microsoft.Extensions.AI abstractions include:
ChatResponseFormat.Json
which represents structured JSON without a particular schema. (learn.microsoft.com)
You can use it through ChatOptions:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
Then:
var response =
await chatClient.GetResponseAsync(
prompt,
options);
The resulting response is intended to be JSON rather than unrestricted prose.
JSON Responses with IChatClient
A current .NET AI application can use IChatClient:
using Microsoft.Extensions.AI;
The abstraction supports ordinary chat requests:
var response =
await chatClient.GetResponseAsync(
"Return customer information as JSON.");
and requests with ChatOptions:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await chatClient.GetResponseAsync(
"Return customer information as JSON.",
options);
The current IChatClient and ChatClientExtensions APIs support these request patterns across .NET 8, 9, 10, and 11 package-provided implementations. (learn.microsoft.com, learn.microsoft.com)
Installing the Required Packages
Create a project:
dotnet new console -n JsonAiDemo
cd JsonAiDemo
Add the Microsoft AI abstractions and a provider integration:
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI
dotnet add package OpenAI
The exact provider package depends on the model service you use.
The important application boundary remains:
IChatClient
rather than provider-specific request code.
Configuring the Chat Client
For an OpenAI-based example:
using Microsoft.Extensions.AI;
using OpenAI;
var apiKey =
Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException(
"OPENAI_API_KEY is not configured.");
var model =
Environment.GetEnvironmentVariable("AI_MODEL")
?? throw new InvalidOperationException(
"AI_MODEL is not configured.");
IChatClient chatClient =
new OpenAIClient(apiKey)
.GetChatClient(model)
.AsIChatClient();
The model name is intentionally configuration-driven so the application is not tied to a specific model identifier.
Basic JSON Response
Start with a simple prompt:
var prompt = """
Analyze this customer message:
"My payment was declined twice."
Return JSON containing:
category
priority
summary
""";
Set the response format:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
Call the model:
var response =
await chatClient.GetResponseAsync(
prompt,
options);
The model should return JSON similar to:
{
"category": "Billing",
"priority": "High",
"summary": "The customer reports repeated payment failures."
}
Reading the JSON Text
The ChatResponse exposes the generated response text.
For example:
var json =
response.Text;
Console.WriteLine(json);
The application can then pass it to System.Text.Json.
System.Text.Json
Modern .NET includes System.Text.Json for JSON serialization and deserialization.
Import:
using System.Text.Json;
Microsoft's current documentation describes JsonSerializer.Deserialize as the standard way to convert JSON text into .NET objects. It supports both generic and non-generic overloads. (learn.microsoft.com)
Creating a C# Class
Suppose the AI returns:
{
"category": "Billing",
"priority": "High",
"summary": "Duplicate payment attempt."
}
Create:
public sealed class TicketResult
{
public string? Category { get; set; }
public string? Priority { get; set; }
public string? Summary { get; set; }
}
Deserialize:
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
Now:
Console.WriteLine(result?.Category);
Console.WriteLine(result?.Priority);
Console.WriteLine(result?.Summary);
The architecture is:
AI
|
v
JSON string
|
v
JsonSerializer.Deserialize<T>()
|
v
TicketResult
Complete Basic Example
using System.Text.Json;
using Microsoft.Extensions.AI;
public sealed class TicketResult
{
public string? Category { get; set; }
public string? Priority { get; set; }
public string? Summary { get; set; }
}
var prompt = """
Analyze this support ticket:
My credit card was charged twice.
Return JSON with:
- category
- priority
- summary
""";
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await chatClient.GetResponseAsync(
prompt,
options);
var json =
response.Text;
if (string.IsNullOrWhiteSpace(json))
{
throw new InvalidOperationException(
"The AI response was empty.");
}
var result =
JsonSerializer.Deserialize<TicketResult>(
json);
if (result is null)
{
throw new InvalidOperationException(
"The AI response could not be deserialized.");
}
Console.WriteLine(
$"Category: {result.Category}");
Console.WriteLine(
$"Priority: {result.Priority}");
This is a complete JSON-based AI pipeline.
JSON Syntax Must Be Valid
JSON has strict syntax rules.
Valid:
{
"name": "Alex",
"age": 25
}
Invalid:
{
name: "Alex",
age: 25
}
JSON property names require double quotes.
Also invalid:
{
"name": 'Alex'
}
JSON strings require double quotes.
This matters because AI models generate text, even when asked for JSON.
JSON Parsing Failures
The following can fail:
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
Possible reasons include:
invalid JSON
truncated response
unexpected property structure
wrong value type
model returned Markdown
empty output
provider-specific response behavior
Use exception handling where appropriate:
try
{
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
}
catch (JsonException ex)
{
Console.WriteLine(
$"Invalid JSON: {ex.Message}");
}
AI JSON Wrapped in Markdown
One common problem is that a model may produce:
```json
{
"category": "Billing"
}
This is Markdown containing JSON rather than raw JSON.
Passing the entire response into:
```csharp
JsonSerializer.Deserialize<T>()
will normally fail.
This is another reason native structured-output or provider-supported JSON response formats are preferable when available.
If you need to support legacy or weakly constrained model behavior, a controlled extraction layer can be implemented, but it should be treated as a fallback rather than the primary architecture.
Why Naive Markdown Stripping Is Risky
A quick workaround might be:
json = json
.Replace("```json", "")
.Replace("```", "");
This can work in simple cases, but it assumes the model used exactly that format.
For production applications, use response-format features when supported, and validate the resulting JSON before processing it.
Case Sensitivity
Suppose the JSON is:
{
"category": "Billing",
"priority": "High"
}
and the C# type is:
public sealed class TicketResult
{
public string? Category { get; set; }
public string? Priority { get; set; }
}
System.Text.Json can map property names according to its configured naming/case behavior.
You can explicitly configure:
var options =
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
Then:
var result =
JsonSerializer.Deserialize<TicketResult>(
json,
options);
Microsoft's current JSON deserialization documentation notes that unmatched JSON properties are ignored by default and that required properties can cause deserialization to fail when absent. (learn.microsoft.com)
Camel Case JSON
Many APIs use camelCase:
{
"firstName": "Alex",
"lastName": "Smith",
"customerId": "C10045"
}
C# typically uses PascalCase:
public sealed class Customer
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? CustomerId { get; set; }
}
Configure:
var options =
new JsonSerializerOptions
{
PropertyNamingPolicy =
JsonNamingPolicy.CamelCase
};
JsonPropertyName
For exact field names, use:
using System.Text.Json.Serialization;
public sealed class Customer
{
[JsonPropertyName("customer_id")]
public string? CustomerId { get; set; }
[JsonPropertyName("first_name")]
public string? FirstName { get; set; }
[JsonPropertyName("last_name")]
public string? LastName { get; set; }
}
Now the expected JSON is:
{
"customer_id": "C10045",
"first_name": "Alex",
"last_name": "Smith"
}
This is helpful when integrating AI output with existing APIs.
Nested JSON
AI responses can contain nested objects.
Example:
{
"customer": {
"name": "Alex",
"email": "alex@example.com"
},
"order": {
"id": "ORD-10025",
"status": "Shipped"
}
}
C#:
public sealed class Customer
{
public string? Name { get; set; }
public string? Email { get; set; }
}
public sealed class Order
{
public string? Id { get; set; }
public string? Status { get; set; }
}
public sealed class SupportResult
{
public Customer? Customer { get; set; }
public Order? Order { get; set; }
}
Deserialize:
var result =
JsonSerializer.Deserialize<SupportResult>(
json);
Nested JSON is handled naturally by System.Text.Json.
JSON Arrays
Suppose the AI returns:
{
"skills": [
"C#",
"ASP.NET Core",
"SQL Server"
]
}
C#:
public sealed class SkillResult
{
public List<string> Skills { get; set; } = [];
}
Deserialize:
var result =
JsonSerializer.Deserialize<SkillResult>(
json);
Then:
foreach (var skill in result!.Skills)
{
Console.WriteLine(skill);
}
Arrays of Objects
Example:
{
"products": [
{
"name": "Laptop",
"category": "Computer"
},
{
"name": "Monitor",
"category": "Display"
}
]
}
C#:
public sealed class Product
{
public string? Name { get; set; }
public string? Category { get; set; }
}
public sealed class ProductResult
{
public List<Product> Products { get; set; } = [];
}
Then:
var result =
JsonSerializer.Deserialize<ProductResult>(
json);
JSON Numbers
AI-generated numerical values require care.
JSON:
{
"quantity": 12,
"price": 499.95
}
C#:
public sealed class ProductOrder
{
public int Quantity { get; set; }
public decimal Price { get; set; }
}
For currency-like application values, decimal is generally preferable to double when the domain requires exact decimal semantics.
JSON Booleans
JSON:
{
"isActive": true
}
C#:
public sealed class CustomerStatus
{
public bool IsActive { get; set; }
}
The application can then use:
if (result.IsActive)
{
...
}
JSON Null Values
AI models may return:
{
"middleName": null
}
C#:
public string? MiddleName { get; set; }
For value types:
public decimal? Total { get; set; }
This is useful when AI is extracting information that may not exist in the source.
Required Properties
Modern C# supports the required keyword:
public sealed class InvoiceData
{
public required string InvoiceNumber { get; set; }
public required string SupplierName { get; set; }
public decimal? Total { get; set; }
}
This expresses the expected application contract.
However, C# required members and JSON schema required fields are related but not identical concepts. For AI applications, verify both the generated schema and deserialization behavior.
System.Text.Json Required Property Behavior
Microsoft's JSON documentation explains that missing required properties can cause deserialization to fail, while unknown JSON properties are ignored by default. (learn.microsoft.com)
This is useful when dealing with AI responses:
Unexpected extra field
->
may be ignored
Missing required field
->
can fail
The exact behavior depends on the type and serializer configuration.
Using JsonRequired
Depending on the target framework and serializer features, you can also use JSON-specific required metadata.
For example:
using System.Text.Json.Serialization;
public sealed class Customer
{
[JsonRequired]
public string? CustomerId { get; set; }
[JsonRequired]
public string? Name { get; set; }
}
This lets serialization metadata explicitly communicate required JSON properties.
JSON Enums
AI models often return:
{
"sentiment": "Positive"
}
C# enum:
public enum Sentiment
{
Positive,
Negative,
Neutral
}
Use:
using System.Text.Json.Serialization;
var serializerOptions =
new JsonSerializerOptions();
serializerOptions.Converters.Add(
new JsonStringEnumConverter());
Then deserialize:
var result =
JsonSerializer.Deserialize<SentimentResult>(
json,
serializerOptions);
Without an appropriate converter, enum string handling can differ from what the application expects.
JSON and AI Classification
A useful example is ticket classification.
public enum TicketCategory
{
Billing,
Delivery,
Account,
Technical,
Other
}
public sealed class TicketClassification
{
public TicketCategory Category { get; set; }
public string? Reason { get; set; }
}
Prompt:
Classify this support ticket.
Return JSON:
{
"category": "...",
"reason": "..."
}
Ticket:
My package has not arrived yet.
Potential output:
{
"category": "Delivery",
"reason": "The customer reports that the package has not arrived."
}
Deserialize and route:
var classification =
JsonSerializer.Deserialize<TicketClassification>(
json,
serializerOptions);
switch (classification!.Category)
{
case TicketCategory.Delivery:
await deliveryQueue.EnqueueAsync(...);
break;
case TicketCategory.Billing:
await billingQueue.EnqueueAsync(...);
break;
}
JSON and AI Extraction
Consider resume extraction.
AI output:
{
"candidateName": "Alex Kumar",
"currentRole": "Software Developer",
"skills": [
"C#",
"ASP.NET Core",
"SQL Server"
],
"yearsOfExperience": 6
}
C#:
public sealed class ResumeData
{
public string? CandidateName { get; set; }
public string? CurrentRole { get; set; }
public List<string> Skills { get; set; } = [];
public int? YearsOfExperience { get; set; }
}
Deserialize:
var resume =
JsonSerializer.Deserialize<ResumeData>(
json);
Now the application can store or display the information.
JSON and AI Document Processing
Documents often produce multiple levels of JSON.
{
"documentType": "Invoice",
"supplier": {
"name": "Example Supplier",
"address": "Example address"
},
"invoice": {
"number": "INV-1005",
"date": "2026-10-02",
"total": 12500.50
},
"items": [
{
"description": "Laptop",
"quantity": 2,
"unitPrice": 5000
}
]
}
C# classes can mirror this structure.
public sealed class Supplier
{
public string? Name { get; set; }
public string? Address { get; set; }
}
public sealed class Invoice
{
public string? Number { get; set; }
public DateTime? Date { get; set; }
public decimal? Total { get; set; }
}
public sealed class InvoiceItem
{
public string? Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
public sealed class InvoiceExtraction
{
public string? DocumentType { get; set; }
public Supplier? Supplier { get; set; }
public Invoice? Invoice { get; set; }
public List<InvoiceItem> Items { get; set; } = [];
}
JSON Date Handling
AI-generated dates require careful validation.
Suppose the model returns:
{
"invoiceDate": "2026-10-02"
}
C#:
public DateTime? InvoiceDate { get; set; }
Deserialization can parse standard JSON date representations, but application-level validation is still important.
For example:
if (invoice.InvoiceDate > DateTime.UtcNow.Date)
{
throw new ValidationException(
"Invoice date cannot be in the future.");
}
The JSON can be valid while the value is still incorrect for the application's business rules.
JSON and Decimal Values
Suppose the model returns:
{
"total": 12500.50
}
Use:
public decimal Total { get; set; }
rather than:
public double Total { get; set; }
when the value represents financial data and decimal precision matters.
Then validate:
if (result.Total < 0)
{
throw new ValidationException(
"Total cannot be negative.");
}
JSON Validation with Data Annotations
Define:
using System.ComponentModel.DataAnnotations;
public sealed class CustomerResult
{
[Required]
public string? CustomerId { get; set; }
[Required]
public string? Name { get; set; }
[EmailAddress]
public string? Email { get; set; }
}
Validate after deserialization:
var validationContext =
new ValidationContext(result);
var validationResults =
new List<ValidationResult>();
var valid =
Validator.TryValidateObject(
result,
validationContext,
validationResults,
validateAllProperties: true);
The architecture becomes:
AI JSON
|
v
Deserialize
|
v
C# Object
|
v
DataAnnotations
|
v
Domain Validation
JSON Validation with FluentValidation
For more complicated applications, FluentValidation can provide richer rules.
Example:
public sealed class TicketResultValidator
: AbstractValidator<TicketResult>
{
public TicketResultValidator()
{
RuleFor(x => x.Category)
.NotEmpty();
RuleFor(x => x.Priority)
.NotEmpty();
RuleFor(x => x.Summary)
.NotEmpty();
}
}
Then:
var validation =
validator.Validate(result);
This creates a reusable validation layer separate from AI generation.
Structural Validation vs Business Validation
These should remain separate.
Structural Validation
Checks:
valid JSON
correct property types
required properties
expected structure
Business Validation
Checks:
price >= 0
quantity > 0
date is valid for the domain
category is allowed
customer has required information
Architecture:
JSON
|
v
Structural Validation
|
v
Business Validation
|
v
Domain Logic
JSON Mode with ChatResponseFormat.Json
Use:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
This is useful when you want JSON but the schema is deliberately flexible.
For example:
{
"keyPoints": [
"Point one",
"Point two"
],
"entities": {
"companies": [
"Example Corp"
]
}
}
The exact structure may vary depending on the input.
Microsoft defines ChatResponseFormat.Json as structured JSON data without a particular schema. (learn.microsoft.com)
When JSON Mode Is Appropriate
JSON mode is useful when:
structure is somewhat flexible
you don't need one strict schema
different input documents produce different optional sections
the application can safely handle optional fields
For example:
AI report
|
+-- summary
+-- keyPoints
+-- optional warnings
+-- optional recommendations
If the application requires an exact contract, use structured JSON Schema instead.
JSON Schema with C#
The next level is:
var responseFormat =
ChatResponseFormat.ForJsonSchema<
TicketResult>();
This generates a schema from the .NET type.
Then:
var options = new ChatOptions
{
ResponseFormat = responseFormat
};
And:
var response =
await chatClient.GetResponseAsync(
prompt,
options);
Current Microsoft documentation provides ForJsonSchema(Type) and ForJsonSchema<T> overloads for generating schema-based response formats. (learn.microsoft.com)
JSON Schema vs JSON Mode
The difference can be visualized as:
JSON MODE
{
"anything": "allowed"
}
versus:
JSON SCHEMA
{
"category": string,
"priority": enum,
"summary": string
}
JSON Schema is useful when downstream application code expects a stable contract.
Explicit JSON Schema
You can also create the schema yourself.
var schema = """
{
"type": "object",
"properties": {
"category": {
"type": "string"
},
"priority": {
"type": "string"
},
"summary": {
"type": "string"
}
},
"required": [
"category",
"priority",
"summary"
],
"additionalProperties": false
}
""";
Parse:
using var schemaDocument =
JsonDocument.Parse(schema);
Create:
var responseFormat =
ChatResponseFormat.ForJsonSchema(
schemaDocument.RootElement,
"TicketResult",
"Support ticket classification");
Then:
var options = new ChatOptions
{
ResponseFormat = responseFormat
};
This approach is useful when the schema comes from an external contract.
C# Type as the Source of Truth
In many applications, it is cleaner to make the C# model the contract:
C# DTO
|
v
JSON Schema
|
v
AI Model
|
v
JSON
|
v
C# DTO
This avoids maintaining the same structure separately in:
C#
+
JSON Schema
+
Prompt text
when a generated schema is sufficient.
Deserializing with JsonSerializer
The most common pattern is:
var result =
JsonSerializer.Deserialize<TicketResult>(
json);
You can also use:
var result =
JsonSerializer.Deserialize(
json,
typeof(TicketResult));
when the type is known at runtime.
For generic infrastructure:
public static object? Deserialize(
string json,
Type targetType)
{
return JsonSerializer.Deserialize(
json,
targetType);
}
Microsoft's current JSON documentation covers both generic and non-generic Deserialize approaches. (learn.microsoft.com)
JsonDocument
Sometimes you do not know the exact structure.
Use:
using var document =
JsonDocument.Parse(json);
Then:
var root =
document.RootElement;
Read a property:
if (root.TryGetProperty(
"category",
out var category))
{
Console.WriteLine(
category.GetString());
}
This is useful for dynamic JSON.
JsonElement
For a flexible response:
using var document =
JsonDocument.Parse(json);
JsonElement root =
document.RootElement;
You can inspect:
root.ValueKind
and:
root.GetProperty("category");
This is useful when the response structure varies.
However, for stable contracts, strongly typed DTOs are easier to maintain.
Dictionary-Based JSON
For flexible objects:
var result =
JsonSerializer.Deserialize<
Dictionary<string, object>>(
json);
But object values can be awkward because JSON values may deserialize into JsonElement instances.
For a stable application contract, use a dedicated type instead.
JsonNode
Another flexible API is JsonNode.
using System.Text.Json.Nodes;
var node =
JsonNode.Parse(json);
var category =
node?["category"]?.GetValue<string>();
This is convenient for dynamic JSON manipulation.
Use:
JsonSerializer
|
+-- typed object mapping
JsonDocument
|
+-- read-only structured inspection
JsonNode
|
+-- mutable JSON tree
for different use cases.
JSON Serialization Back to JSON
The same System.Text.Json APIs can serialize a C# object back to JSON.
var json =
JsonSerializer.Serialize(result);
Pretty-print:
var json =
JsonSerializer.Serialize(
result,
new JsonSerializerOptions
{
WriteIndented = true
});
This is useful when an AI-generated object needs to be returned from an API.
AI JSON to ASP.NET Core JSON
For example:
app.MapPost(
"/api/analyze",
async (
AnalyzeRequest request,
IChatClient chatClient,
CancellationToken cancellationToken) =>
{
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await chatClient.GetResponseAsync(
request.Text,
options,
cancellationToken);
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
return Results.Ok(result);
});
However, a production implementation should explicitly handle:
empty response
JSON parsing failure
validation failure
AI request failure
instead of assuming the model always returns correct JSON.
Better ASP.NET Core Architecture
Use a service:
HTTP Request
|
v
Controller / Endpoint
|
v
AI Service
|
v
IChatClient
|
v
JSON
|
v
JsonSerializer
|
v
DTO
|
v
Validation
|
v
API Response
Example:
public interface IJsonAiService
{
Task<TicketResult> AnalyzeAsync(
string text,
CancellationToken cancellationToken = default);
}
Implementation:
public sealed class JsonAiService
: IJsonAiService
{
private readonly IChatClient _chatClient;
public JsonAiService(
IChatClient chatClient)
{
_chatClient = chatClient;
}
public async Task<TicketResult> AnalyzeAsync(
string text,
CancellationToken cancellationToken = default)
{
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await _chatClient.GetResponseAsync(
"""
Analyze this support ticket and return JSON
with category, priority, and summary.
Ticket:
""" + text,
options,
cancellationToken);
if (string.IsNullOrWhiteSpace(
response.Text))
{
throw new InvalidOperationException(
"AI returned an empty response.");
}
TicketResult? result;
try
{
result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
}
catch (JsonException ex)
{
throw new InvalidOperationException(
"AI returned invalid JSON.",
ex);
}
if (result is null)
{
throw new InvalidOperationException(
"AI returned no usable result.");
}
Validate(result);
return result;
}
private static void Validate(
TicketResult result)
{
if (string.IsNullOrWhiteSpace(
result.Category))
{
throw new ValidationException(
"Category is required.");
}
if (string.IsNullOrWhiteSpace(
result.Summary))
{
throw new ValidationException(
"Summary is required.");
}
}
}
Generic JSON AI Service
You can build reusable infrastructure.
public interface IJsonAiService
{
Task<T> GetJsonAsync<T>(
string prompt,
CancellationToken cancellationToken = default);
}
Implementation:
public sealed class JsonAiService
: IJsonAiService
{
private readonly IChatClient _chatClient;
public JsonAiService(
IChatClient chatClient)
{
_chatClient = chatClient;
}
public async Task<T> GetJsonAsync<T>(
string prompt,
CancellationToken cancellationToken = default)
{
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await _chatClient.GetResponseAsync(
prompt,
options,
cancellationToken);
if (string.IsNullOrWhiteSpace(
response.Text))
{
throw new InvalidOperationException(
"AI returned empty JSON.");
}
var result =
JsonSerializer.Deserialize<T>(
response.Text);
if (result is null)
{
throw new InvalidOperationException(
"JSON could not be converted to the requested type.");
}
return result;
}
}
Usage:
var result =
await jsonAiService.GetJsonAsync<TicketResult>(
prompt,
cancellationToken);
This is useful for generic application infrastructure.
Generic Structured Output vs Generic JSON
There is an important distinction.
Generic JSON service:
GetJsonAsync<T>()
may use:
ChatResponseFormat.Json
and then deserialize the result.
Structured-output API:
GetResponseAsync<T>()
uses the Microsoft structured-output extension and can request schema-based output when supported. (learn.microsoft.com)
The comparison is:
JSON Mode
AI
|
v
JSON
|
v
JsonSerializer
|
v
T
versus:
Structured Output
T
|
v
JSON Schema
|
v
AI
|
v
JSON
|
v
ChatResponse<T>
The second provides a tighter contract when the client/model supports it.
When to Use Raw JSON
Raw JSON is useful when:
response structure is dynamic
schema is not known at compile time
you need flexible document-like data
you are building a generic JSON service
For example:
{
"summary": "...",
"entities": {
"people": [],
"companies": [],
"locations": []
}
}
The exact content can vary.
When to Use Typed JSON
Use a C# type when:
fields are known
application logic depends on the response
database mapping is required
workflow routing depends on values
API contracts are stable
For example:
TicketResult
InvoiceResult
ResumeResult
ProductResult
JSON Responses and Prompt Templates
Combine the previous topic's prompt-template architecture with JSON output.
Template:
Analyze the following support ticket.
Ticket:
{{ticket}}
Return a JSON object with:
- category
- priority
- summary
C#:
var prompt =
renderer.Render(
template,
new Dictionary<string, string>
{
["ticket"] = ticketText
});
JSON response:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await chatClient.GetResponseAsync(
prompt,
options);
Deserialize:
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
Architecture:
Prompt Template
|
v
Rendered Prompt
|
v
IChatClient
|
v
JSON Response
|
v
System.Text.Json
|
v
C# DTO
JSON Responses and System Prompts
A system prompt can establish JSON behavior:
You are a ticket classification assistant.
Always return a JSON object.
The object must contain:
- category
- priority
- summary
Do not return Markdown.
Do not add explanatory text outside the JSON object.
Then:
var messages = new List<ChatMessage>
{
new(
ChatRole.System,
systemPrompt),
new(
ChatRole.User,
ticketText)
};
And:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await chatClient.GetResponseAsync(
messages,
options);
The response-format setting and prompt instructions reinforce each other, although application code should still validate the result.
JSON Responses and Structured Outputs
For strict application contracts, combine both ideas:
var response =
await chatClient.GetResponseAsync<
TicketResult>(
messages);
This is the simplest modern typed pattern.
The current ChatClientStructuredOutputExtensions class provides generic GetResponseAsync<T> overloads for strings, individual messages, and collections of messages, with optional JsonSerializerOptions, ChatOptions, and control over JSON Schema response format. (learn.microsoft.com)
Explicit JSON Schema with ChatOptions
You can also control the schema explicitly:
var responseFormat =
ChatResponseFormat.ForJsonSchema<TicketResult>();
var options = new ChatOptions
{
ResponseFormat = responseFormat
};
var response =
await chatClient.GetResponseAsync(
messages,
options,
cancellationToken);
This gives the low-level request explicit schema information while leaving deserialization under your control.
JSON Serialization Options
A useful production setup might be:
var serializerOptions =
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy =
JsonNamingPolicy.CamelCase,
WriteIndented = false
};
Then:
var result =
JsonSerializer.Deserialize<TicketResult>(
json,
serializerOptions);
For enum strings:
serializerOptions.Converters.Add(
new JsonStringEnumConverter());
JSON Source Generation
For high-performance scenarios, source-generated serialization can be useful.
Example:
using System.Text.Json.Serialization;
[JsonSerializable(typeof(TicketResult))]
public partial class AiJsonContext
: JsonSerializerContext
{
}
Then use the generated metadata with the serializer:
var result =
JsonSerializer.Deserialize(
json,
AiJsonContext.Default.TicketResult);
This can be useful when:
Native AOT
trimming
high throughput
serialization-heavy APIs
are important requirements.
The application should still verify that the source-generated metadata aligns with the AI response contract.
JSON Performance Considerations
For a normal AI request, model latency usually dominates JSON parsing time.
For example:
AI generation ~1000+ ms
JSON parsing ~1 ms
But high-throughput systems can still benefit from efficient serialization.
Avoid unnecessary conversion cycles:
JSON
-> string
-> object
-> JSON
-> string
unless each conversion serves a real purpose.
Streaming JSON
A normal streaming AI response arrives incrementally.
For example:
{
"category":
then:
"Billing",
then:
"priority":
and so on.
This creates an important problem:
Partial JSON
!=
Valid JSON
You should not assume every streamed update is independently parseable JSON.
The current IChatClient supports streaming through GetStreamingResponseAsync, but ordinary streaming is different from receiving one finalized JSON document. (learn.microsoft.com)
For structured application processing, it is often simpler to collect the completed content first and then deserialize it.
Streaming JSON Architecture
AI Model
|
+-- chunk 1
+-- chunk 2
+-- chunk 3
+-- chunk 4
|
v
Complete JSON
|
v
System.Text.Json
|
v
C# Object
Do not deserialize every chunk independently unless you have deliberately designed a streaming JSON parser.
JSON Responses and Error Handling
A robust JSON AI service should handle:
empty response
invalid JSON
wrong property type
missing property
unexpected structure
schema mismatch
business validation failure
provider failure
timeout
cancellation
Example:
try
{
var response =
await chatClient.GetResponseAsync(
prompt,
options,
cancellationToken);
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text);
if (result is null)
{
throw new InvalidOperationException(
"AI JSON result is null.");
}
Validate(result);
return result;
}
catch (JsonException ex)
{
logger.LogError(
ex,
"AI returned invalid JSON.");
throw;
}
Handling Invalid JSON with Retry
A controlled retry can sometimes be useful:
Request
|
v
AI
|
v
Invalid JSON
|
v
Retry with corrected request
|
v
JSON
For example, the retry prompt might restate:
Return only valid JSON.
Do not use Markdown code fences.
Do not include explanatory text.
However, a retry should not hide systematic provider/model incompatibilities.
Better Retry Decision
Use the failure type:
Invalid JSON
|
+-- transient model output issue
| |
| v
| retry
|
+-- model doesn't support requested format
|
v
configuration change
This is more reliable than retrying every exception indiscriminately.
JSON Responses from AI Models and Security
JSON does not automatically make AI output trustworthy.
Suppose the AI returns:
{
"userId": "12345",
"role": "Admin"
}
The JSON may be valid, but the value may be incorrect.
Never use model-generated authorization claims as the source of truth.
Use:
Identity Provider
+
Authorization System
+
Database
+
Application Policies
for security-sensitive decisions.
JSON Injection vs Prompt Injection
These are different concerns.
Prompt Injection
Untrusted content attempts to influence the model.
Example:
Ignore previous instructions.
JSON Injection
Application-generated or concatenated JSON becomes malformed or unsafe.
The safest approach is to use serialization:
JsonSerializer.Serialize(value);
instead of manually building JSON strings.
Avoid:
var json =
$"{{\"name\":\"{userName}\"}}";
Prefer:
var json =
JsonSerializer.Serialize(
new
{
name = userName
});
Never Build AI JSON by String Concatenation
Bad:
var prompt =
"{ \"customer\": \"" +
customerName +
"\" }";
Better:
var payload =
new
{
customer = customerName
};
var json =
JsonSerializer.Serialize(payload);
This avoids escaping mistakes and malformed JSON.
JSON Escaping
Suppose a user name is:
Alex "AJ" Kumar
Manual string construction could produce invalid JSON.
JsonSerializer.Serialize handles escaping correctly:
var json =
JsonSerializer.Serialize(
new
{
name = "Alex \"AJ\" Kumar"
});
Result:
{
"name": "Alex \"AJ\" Kumar"
}
Use the serializer whenever application data needs to become JSON.
AI-Generated JSON Should Not Be Executed Directly
Suppose the AI returns:
{
"sql": "DROP TABLE Users;"
}
Even though the JSON is valid, the content is dangerous.
The application must treat the value as untrusted.
Architecture:
AI JSON
|
v
Parse
|
v
Validate
|
v
Policy
|
v
Execute only if explicitly allowed
For SQL assistants, prefer structured query intents or tightly validated read-only operations rather than blindly executing AI-generated SQL.
JSON Responses and Tool Calling
Structured JSON can also represent tool arguments.
Example:
{
"orderId": "ORD-10025",
"action": "getStatus"
}
C#:
public sealed class OrderToolRequest
{
public string? OrderId { get; set; }
public string? Action { get; set; }
}
The application can deserialize the object and enforce:
action allowed?
order exists?
user authorized?
This is safer than interpreting arbitrary text as tool commands.
JSON Responses and RAG
A RAG system can return:
{
"answer": "The application supports Windows 11.",
"sources": [
{
"title": "Installation Guide",
"page": 4
}
]
}
C#:
public sealed class RagResult
{
public string? Answer { get; set; }
public List<RagSource> Sources { get; set; } = [];
}
public sealed class RagSource
{
public string? Title { get; set; }
public int Page { get; set; }
}
Now the UI can render sources independently from the answer.
JSON Responses and Agents
An agent can produce:
{
"nextAction": "Search",
"query": "ASP.NET Core authentication documentation"
}
C#:
public sealed class AgentDecision
{
public string? NextAction { get; set; }
public string? Query { get; set; }
}
Then:
if (decision.NextAction == "Search")
{
await searchService.SearchAsync(
decision.Query!);
}
Again, validate that action before execution.
JSON Responses and Workflow Engines
Structured AI output can become a workflow event:
{
"eventType": "InvoiceExtracted",
"invoiceNumber": "INV-1005",
"total": 12500.50
}
Then:
AI
|
v
JSON
|
v
InvoiceExtracted
|
+-- Accounting
+-- Reporting
+-- Notification
The AI becomes one stage in a conventional software workflow.
JSON Responses and SQL Server
Suppose AI extracts:
{
"customerId": "C10045",
"requestedAction": "FindOpenOrders"
}
C#:
public sealed class OrderSearchRequest
{
public string? CustomerId { get; set; }
public string? RequestedAction { get; set; }
}
Then application code decides which SQL operation is allowed:
const string sql = """
SELECT
Id,
Status,
Total
FROM Orders
WHERE CustomerId = @CustomerId
AND Status = 'Open';
""";
The AI never supplies the SQL directly.
JSON Responses and Entity Framework Core
Use the structured result to construct an EF Core query:
var orders =
await db.Orders
.Where(x =>
x.CustomerId ==
request.CustomerId &&
x.Status == OrderStatus.Open)
.ToListAsync(
cancellationToken);
This is safer than letting the AI produce arbitrary database commands.
JSON Responses and APIs
Suppose an external API expects:
{
"customerId": "C10045",
"status": "active"
}
Create:
public sealed class CustomerUpdateRequest
{
public string? CustomerId { get; set; }
public string? Status { get; set; }
}
Serialize:
var payload =
JsonSerializer.Serialize(
request);
Send using HttpClient.
This provides:
AI
|
v
C# DTO
|
v
Validation
|
v
JsonSerializer
|
v
External API
JSON Serialization with HttpClient
Example:
var response =
await httpClient.PostAsJsonAsync(
"/api/customers",
request,
cancellationToken);
For standard ASP.NET Core and .NET APIs, this is usually easier than manually calling JsonSerializer.Serialize.
JSON Naming Policy Across APIs
You can standardize application JSON:
builder.Services.ConfigureHttpJsonOptions(
options =>
{
options.SerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.CamelCase;
});
Then APIs consistently emit:
{
"customerId": "C10045"
}
while C# uses:
CustomerId
JSON Schema and Application Contracts
A useful enterprise architecture is:
C# Type
|
v
JSON Schema
|
+---------+---------+
| |
v v
AI Request API Contract
|
v
AI Model
|
v
JSON
|
v
C# Type
This reduces mismatch between AI output and application expectations.
Versioning JSON Contracts
Suppose:
TicketResult v1
contains:
{
"category": "Billing",
"summary": "..."
}
Version 2 adds:
{
"category": "Billing",
"summary": "...",
"priority": "High"
}
That is an output-contract change.
Version the schema or DTO when changes may affect downstream services.
JSON Schema Compatibility
Safer evolution:
v1
|
+-- add optional field
|
v
v2
More disruptive changes:
rename field
change type
remove required property
change enum values
These should be treated as breaking contract changes.
Logging JSON Responses
Useful metadata:
operation
prompt name
prompt version
schema name
schema version
model
request ID
duration
JSON parse success
validation status
Avoid logging sensitive content by default.
For example:
logger.LogInformation(
"AI JSON operation {Operation} using schema {Schema} completed. ParseSuccess={ParseSuccess}",
operation,
schemaName,
true);
Measuring JSON Parse Failures
Track:
total requests
valid JSON
invalid JSON
deserialization failures
validation failures
business-rule failures
For example:
10,000 requests
|
+-- 9,910 valid JSON
+-- 60 parsing failures
+-- 30 validation failures
This provides a useful reliability metric.
Observability Architecture
AI Request
|
v
AI Model
|
v
JSON
|
+---- Parse Success
|
+---- Parse Failure
|
+---- Validation Success
|
+---- Validation Failure
|
v
Application
Track these stages separately.
Unit Testing JSON Deserialization
You can test without an AI model.
[Fact]
public void ValidTicketJsonDeserializes()
{
var json = """
{
"category": "Billing",
"priority": "High",
"summary": "Duplicate payment"
}
""";
var result =
JsonSerializer.Deserialize<TicketResult>(
json);
Assert.NotNull(result);
Assert.Equal(
"Billing",
result.Category);
}
Testing Invalid JSON
[Fact]
public void InvalidJsonThrows()
{
var json = """
{
"category": "Billing"
""";
Assert.Throws<JsonException>(
() =>
JsonSerializer.Deserialize<TicketResult>(
json));
}
This tests your parsing layer independently of the AI service.
Testing Missing Fields
Suppose:
{
"priority": "High"
}
and Category is required by your application.
After deserialization:
var result =
JsonSerializer.Deserialize<TicketResult>(
json);
then run domain validation:
Validate(result);
This keeps:
JSON parsing
separate from:
business validation
Integration Testing AI JSON
Create representative prompts:
Test 1
Payment was charged twice.
Test 2
My package has not arrived.
Test 3
I cannot log into my account.
Test 4
The application crashes during startup.
For each response:
AI
|
v
JSON
|
v
Deserialize
|
v
Validate
|
v
Assert
Example:
Assert.Equal(
TicketCategory.Billing,
result.Category);
AI Evaluation for JSON
Measure:
JSON validity
schema compliance
field completeness
semantic accuracy
enum accuracy
numeric accuracy
business-rule compliance
This is better than checking only:
"Did the request return HTTP 200?"
Common JSON Mistakes
Asking for JSON but not configuring a JSON response format
A textual instruction alone may not provide enough reliability.
Prefer:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
when the configured client/model supports it.
Parsing AI output with regex
Avoid:
Regex.Match(...)
for general JSON.
Use:
JsonSerializer
Assuming the response is always valid
AI output can fail.
Always validate.
Ignoring type mismatches
This:
{
"price": "unknown"
}
does not match:
decimal Price
unless your serialization configuration explicitly supports the conversion.
Mixing JSON parsing and business rules
Keep them separate.
Storing unvalidated AI data
Validate before persistence.
Treating valid JSON as trusted data
Valid syntax does not mean valid business information.
Returning raw model JSON directly to clients
An internal AI response may contain fields your API should not expose.
Map to the API contract.
Building JSON strings manually
Use JsonSerializer.Serialize.
Using flexible JSON when a fixed contract is required
If application logic depends on known properties, define a type or schema.
Frequently Asked Questions
What is a JSON response from an AI model?
It is model-generated output that follows JSON syntax so that application code can parse the result programmatically.
Why use JSON instead of plain text?
JSON provides named fields and a machine-readable structure.
How do I request JSON from IChatClient?
Use:
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
Then pass the options to GetResponseAsync. ChatResponseFormat.Json represents structured JSON without a fixed schema. (learn.microsoft.com)
How do I deserialize the AI JSON?
Use:
var result =
JsonSerializer.Deserialize<MyType>(
json);
Microsoft recommends System.Text.Json for JSON deserialization in modern .NET. (learn.microsoft.com)
What is System.Text.Json?
It is the built-in .NET JSON serialization library used to serialize and deserialize JSON data.
What is the difference between JSON mode and structured output?
JSON mode requests JSON without a fixed schema.
Structured output requests a response matching a defined type/schema.
Can I use GetResponseAsync<T>?
Yes. Current Microsoft.Extensions.AI structured-output extensions provide generic GetResponseAsync<T> overloads for strings, messages, and message collections. (learn.microsoft.com)
When should I use GetResponseAsync<T> instead of manual JSON deserialization?
Use the typed API when the output structure is known and your configured model/client supports schema-based structured output.
Use manual JSON handling when the response is intentionally flexible or your application needs explicit control over parsing.
Can JSON responses contain nested objects?
Yes.
Can JSON responses contain arrays?
Yes.
Can JSON responses contain enums?
Yes, with appropriate System.Text.Json configuration when the model returns enum names as strings.
Can JSON responses contain nullable properties?
Yes.
What happens when JSON is invalid?
JsonSerializer.Deserialize can throw JsonException. The application should handle the failure according to its error strategy.
What happens when an AI response is wrapped in Markdown code fences?
Raw deserialization will normally fail because the entire response is no longer a JSON document. Prefer a provider/client response-format mechanism that returns JSON directly when supported.
Can I use JSON with ASP.NET Core?
Yes. JSON can be deserialized into a C# object and then returned through a minimal API or controller.
Should I use JSON directly as an EF Core entity?
No. Use an AI DTO, validate it, map it to a domain/persistence model, and then save it.
Is JSON Schema better than JSON mode?
When the application requires a stable response contract, JSON Schema provides stronger structure than schema-less JSON. When flexibility is intentional, JSON mode may be sufficient.
Can AI JSON be trusted?
No. JSON validity does not prove that the values are correct or authorized.
Can JSON responses be used with RAG?
Yes. A RAG answer can include fields such as answer, citations, sources, confidence metadata, or extracted entities.
Can JSON responses be used with agents?
Yes. Agents can return structured decisions, tool parameters, workflow states, and other machine-readable information.
Can JSON responses be streamed?
The base IChatClient supports streaming, but a streamed JSON document is normally incomplete until enough chunks have arrived to form a complete JSON value. For most structured application processing, collect the completed response before deserialization. (learn.microsoft.com)
Should I use Newtonsoft.Json instead?
System.Text.Json is the built-in modern .NET JSON stack and integrates naturally with current .NET APIs. Newtonsoft.Json remains useful in scenarios that require features specific to that ecosystem, but it is not required for normal AI JSON processing.
Interview Questions
What is ChatResponseFormat.Json?
It is the Microsoft.Extensions.AI response-format object representing structured JSON without a particular schema. (learn.microsoft.com)
How do you deserialize AI-generated JSON in C#?
Use:
JsonSerializer.Deserialize<T>(json);
Why use System.Text.Json?
It is part of modern .NET and provides built-in JSON serialization and deserialization.
What is the difference between JsonDocument and JsonSerializer?
JsonSerializer maps JSON to .NET types.
JsonDocument provides a DOM-like representation for inspecting JSON dynamically.
When would you use JsonNode?
When you need a mutable JSON tree or flexible JSON manipulation.
How do you handle invalid AI JSON?
Catch parsing errors, log appropriate metadata, and use a controlled retry, fallback, or failure path.
Can JSON mode guarantee a particular schema?
No. JSON mode requests structured JSON but does not define one exact schema. Use JSON Schema or the typed structured-output API when a fixed contract is required.
Why use enums in AI JSON models?
Enums restrict values to a known set and simplify downstream logic.
Why use DTOs instead of EF Core entities?
DTOs create a boundary between untrusted AI output and persistence models.
What is schema-less JSON?
JSON whose top-level structure is not constrained by one predefined JSON Schema.
What is structured JSON?
JSON that is expected to conform to an explicit structure, often represented by a JSON Schema or C# type.
Why validate after deserialization?
Because successful parsing only establishes structural compatibility. It does not prove that the value satisfies business rules.
How can you avoid manually constructing JSON?
Use:
JsonSerializer.Serialize(value);
or ASP.NET Core helpers such as:
PostAsJsonAsync(...)
What is a JSON response contract?
It is the expected shape and semantics of the data exchanged between the AI layer and application code.
Best Practices
Use System.Text.Json
For modern .NET applications, it is the natural JSON serialization library.
Use response-format features
When the configured model/client supports structured JSON, use:
ChatResponseFormat.Json
or schema-based structured output.
Prefer typed models for stable contracts
Use:
TicketResult
InvoiceResult
ResumeResult
when the structure is known.
Validate JSON before business processing
Use:
parse
->
validate
->
process
Separate AI DTOs from domain entities
Use:
AI JSON
->
DTO
->
Validation
->
Domain
->
Database
Handle parsing failures explicitly
Do not assume AI responses are always valid JSON.
Avoid regex-based JSON parsing
Use System.Text.Json.
Do not manually concatenate JSON
Serialize objects using:
JsonSerializer.Serialize(...)
Keep security outside the AI response
Authentication and authorization should not depend on AI-generated JSON.
Track schema and prompt versions
This makes production debugging easier.
Keep output contracts focused
Do not create one giant JSON object containing every possible field in the application.
Minimize sensitive data
Only extract, store, or log the information the application actually needs.
Learning Path
A practical path is:
1. Plain text AI responses
|
v
2. JSON response prompts
|
v
3. ChatResponseFormat.Json
|
v
4. System.Text.Json
|
v
5. JSON deserialization
|
v
6. Nested JSON
|
v
7. Arrays and enums
|
v
8. JSON validation
|
v
9. JSON Schema
|
v
10. Typed structured output
|
v
11. ASP.NET Core JSON APIs
|
v
12. AI workflows and RAG
Practical Project: AI JSON Ticket Analyzer
Build an ASP.NET Core application with:
POST /api/tickets/analyze
Input:
{
"text": "My payment was charged twice."
}
AI output:
{
"category": "Billing",
"priority": "High",
"summary": "Customer reports a duplicate payment.",
"suggestedAction": "Escalate the ticket to billing."
}
Response Model
public enum TicketCategory
{
Billing,
Delivery,
Account,
Technical,
Other
}
public enum TicketPriority
{
Low,
Medium,
High,
Critical
}
public sealed class TicketResult
{
public TicketCategory Category { get; set; }
public TicketPriority Priority { get; set; }
public string? Summary { get; set; }
public string? SuggestedAction { get; set; }
}
JSON Serializer Configuration
var serializerOptions =
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
serializerOptions.Converters.Add(
new JsonStringEnumConverter());
AI Request
var options = new ChatOptions
{
ResponseFormat = ChatResponseFormat.Json
};
var response =
await chatClient.GetResponseAsync(
"""
Analyze the following support ticket.
Return JSON containing:
category
priority
summary
suggestedAction
Ticket:
My payment was charged twice.
""",
options,
cancellationToken);
Deserialize
var result =
JsonSerializer.Deserialize<TicketResult>(
response.Text,
serializerOptions);
Validate
if (result is null)
{
throw new InvalidOperationException(
"AI response was empty.");
}
if (string.IsNullOrWhiteSpace(
result.Summary))
{
throw new ValidationException(
"Summary is required.");
}
Return from ASP.NET Core
return Results.Ok(result);
Final API response:
{
"category": "Billing",
"priority": "High",
"summary": "Customer reports a duplicate payment.",
"suggestedAction": "Escalate the ticket to billing."
}
Advanced Project: AI JSON Document Processor
Build:
Document Upload
|
v
Text Extraction
|
v
Prompt Template
|
v
AI Model
|
v
JSON
|
v
System.Text.Json
|
v
DocumentResult
|
+---- Validation
|
+---- Database
|
+---- Search Index
|
+---- API
Supported document types:
invoice
resume
contract
purchase order
support ticket
product description
Each document type has its own C# contract.
Advanced Project: AI JSON Workflow Engine
Create a workflow result:
public enum WorkflowAction
{
Approve,
Reject,
Escalate,
RequestInformation
}
public sealed class WorkflowDecision
{
public WorkflowAction Action { get; set; }
public string? Reason { get; set; }
public List<string> RequiredInformation { get; set; } = [];
}
AI:
Document
|
v
WorkflowDecision JSON
|
+-- Approve
+-- Reject
+-- Escalate
+-- RequestInformation
Application code handles the final action.
This creates a clean separation:
AI
=
interpretation
Application
=
execution
Production Architecture
A mature JSON-based AI system can look like:
+----------------------+
| ASP.NET Core API |
+----------+-----------+
|
v
+----------------------+
| AI Application |
| Service |
+----------+-----------+
|
+-------------------+-------------------+
| | |
v v v
Prompt Template Context Manager Schema
| | |
+-------------------+-------------------+
|
v
IChatClient
|
v
AI Model
|
v
JSON
|
v
System.Text.Json
|
v
DTO
|
+----------+----------+
| |
v v
Validation Business Rules
| |
+----------+----------+
|
v
Domain Application
|
+--------------------+---------------------+
| | |
v v v
SQL Server API Queue
JSON Reliability Checklist
Before using AI-generated JSON in production, verify:
[ ] AI JSON response format is explicitly configured
[ ] Empty responses are handled
[ ] Invalid JSON is handled
[ ] DTOs are defined
[ ] Serializer options are deliberate
[ ] Enum handling is configured
[ ] Required fields are validated
[ ] Business rules are validated
[ ] Sensitive fields are protected
[ ] AI DTOs are separate from database entities
[ ] Prompt version is tracked
[ ] Schema version is tracked
[ ] Parse failures are monitored
[ ] Retry strategy is controlled
[ ] Model/provider capabilities are known
Key Takeaways
JSON is one of the most useful boundaries between AI generation and application code.
The basic flow is:
Prompt
|
v
AI Model
|
v
JSON
|
v
JsonSerializer
|
v
C# Object
Use ChatResponseFormat.Json when you need structured JSON without a fixed schema. Microsoft currently defines this format specifically as JSON data without a particular schema. (learn.microsoft.com)
Use typed structured output when the response contract is known:
var response =
await chatClient.GetResponseAsync<MyType>(
prompt);
The current .NET AI structured-output extensions provide these typed APIs and can use JSON Schema when the underlying model/client supports it. (learn.microsoft.com)
Use System.Text.Json when you need explicit control over JSON:
var result =
JsonSerializer.Deserialize<MyType>(
json);
The production pipeline should be:
AI JSON
|
v
Parsing
|
v
Structural Validation
|
v
Business Validation
|
v
Domain Logic
|
v
Database / API / Workflow
The critical principle is:
Valid JSON
!=
Correct Data
!=
Authorized Action
JSON makes AI responses easier to process, but application code must still validate the meaning and permissions associated with those responses.

Post a Comment