View : 128

01/06/2026 04:42am

Implementation of JSON Mode and Structured Output in Go using OpenAI API

Golang The Series EP.147: Structured Output - Enforcing JSON Responses

#Golang The Series

#Golang

#Go

#JSON Mode

#JSON

#Structured Output

#AI API

#Backend Development

Welcome to EP.147! In our previous episode, we mastered the art of writing prompts to communicate with AI. However, the most persistent headache for Gophers is the "chatty" nature of AI. It often insists on adding polite fluff like, "Certainly! Here is the JSON you requested..." or concluding with, "I hope this helps!"

These extra sentences are the primary culprits that cause Go’s json.Unmarshal function to crash, simply because the response isn't Pure JSON. Today, we’re going to solve this once and for all using Structured Output techniques to force the AI to return data that fits our Go structs with 100% precision.

Designing Go Structs for AI Output

The core idea isn't just creating a random struct; it’s about making that struct serve as a Contract between your Go code and the AI. There are three key areas to focus on:

  • JSON Tags are the Law: The names defined in json:"..." are the exact field names the AI must output. Remember, these are case-sensitive!

  • Appropriate Data Types: If you need a numeric score, use int or float64. This allows Go to automatically validate the data type during unmarshaling.

  • Data Possibility: If a field might be missing or "null" from the AI's perspective, consider using Pointers (e.g., *string) to handle those null values gracefully without failing.

Example Structure:

Go

type AnalysisResult struct {
    // Use standard, descriptive field names
    Sentiment string   `json:"sentiment"` 
    Score     int      `json:"score"`
    Keywords  []string `json:"keywords"`
    Summary   string   `json:"summary"`
}

💡 Pro-Tip for Gophers:

Using descriptive English names in your JSON tags (like sentiment instead of just s) significantly helps the AI understand the context of the data it needs to provide. You barely need to explain the fields in your prompt because the AI is already trained to understand the semantic meaning of well-named variables!

JSON Mode and System Prompts

In the past, we had to pray that the AI would obey our "Please respond in JSON" command. Today, modern APIs like GPT-4o, or even local models running via Ollama, feature a specialized JSON Mode. This enforces a constraint at the engine level: "You must not return anything except valid JSON."

The Golden Rule!

Even with this mode enabled, OpenAI has a strict requirement: You must include the word "JSON" in your messages (ideally within the System Prompt). If you forget to mention it, the API will immediately return an error.

Configuration Example in Go:

Go

req := openai.ChatCompletionRequest{
    Model: openai.GPT4o,
    // CRITICAL: Enforce structure at the JSON Object level
    ResponseFormat: &openai.ChatCompletionResponseFormat{
        Type: openai.ChatCompletionResponseFormatTypeJSONObject,
    },
    Messages: []openai.ChatCompletionMessage{
        {
            Role:    openai.ChatMessageRoleSystem,
            // The word "JSON" must be present here!
            Content: "You are a helpful assistant designed to output JSON.",
        },
        {
            Role:    openai.ChatMessageRoleUser,
            Content: "Analyze this feedback: 'I love this gopher tool!'",
        },
    },
}

🛡️ Why use JSON Mode?

Using this mode guarantees that the output won't contain introductory fluff like "Here is your data:". The resulting JSON will always be well-formed (properly closed braces), significantly reducing the burden of complex error handling in our Go code.

Parsing and Error Handling

Once the AI responds with a JSON string, our job as Go developers is to transform that data into a Typed Struct. This allows us to access fields safely (Type Safety) using the json.Unmarshal function.

Implementation Example:

Go

var result AnalysisResult

// Extract content from AI Response
content := resp.Choices[0].Message.Content

// Convert from String/Byte Slice into the Struct
err := json.Unmarshal([]byte(content), &result)
if err != nil {
    // In production, log/alert or implement Retry Logic
    log.Printf("Error: AI returned invalid JSON: %v", err)
    return
}

// Access data immediately with Strong Typing!
fmt.Printf("Sentiment: %s (Score: %d)\n", result.Sentiment, result.Score)

⚠️ Warning: Don't Trust the AI 100%

Even with JSON Mode active, a professional developer builds a Robust system that survives edge cases using these techniques:

  1. Validation: After unmarshaling, validate the values (e.g., Is the Score actually between 0-100?). The AI might provide a correct JSON structure but hallucinate the actual content.

  2. Graceful Recovery: Instead of using log.Fatalf (which kills your program), handle errors gracefully. Return a default value or send a follow-up prompt to the AI to fix its own mistake (Self-Correction).

  3. The "Markdown Trap": Some models (especially Local LLMs) might still wrap the JSON in markdown blocks (e.g., json ... ). It is best practice to write a small helper function to Trim or strip these characters before passing the string to json.Unmarshal.

Why use Structured Output?

For a Gopher, having an AI communicate in JSON isn't just about convenience—it’s about transforming the AI into a reliable Microservice within your architecture.

  • Reliability: Your system becomes significantly more stable. Because we know the data type in advance, we don't have to worry about the AI responding with prose or poetry. This drastically reduces the chances of your program encountering a Panic or unexpected errors.

  • Automation: Once data is in JSON format, you can plug it into your workflow immediately. Whether it's inserting results into a Database, firing a Webhook to your support team, or passing the data to other services in your system, it happens seamlessly without manual intervention.

  • Type Safety: We leverage the core strengths of the Go language—performing strict validation, checking for missing fields, and managing complex nested structures with absolute confidence.

🎯 Daily Mission

To put this into practice, try building a small Go program that takes a Product Review from a user and uses a prompt to extract data into the following JSON struct:

Go

type ReviewAnalysis struct {
    IsPositive  bool   `json:"is_positive"`
    ProductName string `json:"product_name"`
    DefectFound string `json:"defect_found"`
}

The Homework Challenge:

Try sending sentences that "do not mention a product name" or "have no defects reported." Observe how the AI handles it. Will it return an empty string "", a null, or skip the field entirely?

Hint: Try updating your struct to use Pointers (e.g., *string) and observe the difference during unmarshaling. You'll see how much better it handles data that "wasn't sent"!


Conclusion

Implementing Structured Output is what evolves an AI from a chatty bot into a professional Data Processor that speaks the same language as our backend. When you control the output 100%, the boundaries of what you can build expand exponentially.

Coming Up Next | EP.148: Handling Streams

Now that we can manage data with precision, it's time to impress users with speed! We’ll be building a real-time chat system using Go Channels to stream text just like the real ChatGPT. Get ready to dive deep into Go Concurrency—see you there!

Follow Superdev Academy on all platforms: