/n8n-tutorials

How to get consistent JSON outputs from a language model in n8n?

Learn how to get consistent JSON outputs from language models in n8n with step-by-step techniques including prompt structuring, function calling, validation, error handling, and caching for reliable workflow automation.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to get consistent JSON outputs from a language model in n8n?

To get consistent JSON outputs from a language model in n8n, you need to properly structure your prompts, use function calling or JSON mode if available, and implement error handling for malformed responses. This comprehensive guide will walk you through various techniques to ensure reliable JSON responses from language models within the n8n workflow automation platform.

 

Step 1: Understanding the Challenge of JSON Generation in Language Models

 

Before diving into implementation, it's important to understand why language models sometimes struggle with consistent JSON output:

  • Language models are trained to generate natural language, not structured data formats
  • They may introduce extra text, explanations, or formatting that breaks JSON syntax
  • Without proper constraints, they might generate incomplete JSON structures
  • Responses can vary between calls even with identical prompts

 

Step 2: Setting Up n8n for Language Model Integration

 

First, ensure you have a functioning n8n instance with the necessary nodes:

  • Install n8n (if not already installed) via npm: npm install n8n -g
  • Start n8n with: n8n start
  • Access the n8n dashboard (usually at http://localhost:5678)
  • Create a new workflow by clicking "Create new workflow"

 

Step 3: Adding a Language Model Integration Node

 

n8n supports various AI service providers. Here's how to add an OpenAI node (one of the most common):

  • In your workflow, click the "+" button to add a new node
  • Search for "OpenAI" and select the OpenAI node
  • Configure the API authentication with your OpenAI API key
  • Select the appropriate model (GPT-4, GPT-3.5-turbo, etc.)
  • Configure basic parameters like temperature (lower values like 0.1-0.3 for more deterministic outputs)

 

Step 4: Using OpenAI Function Calling for JSON Generation

 

Function calling is one of the most reliable methods for getting structured JSON outputs:

  • In the OpenAI node, select "Function Calling" as the Output
  • Define the function schema that specifies the exact JSON structure you need

{
  "name": "generate_product_data",
  "description": "Generate product information in a structured format",
  "parameters": {
    "type": "object",
    "required": ["name", "price", "category", "features"],
    "properties": {
      "name": {
        "type": "string",
        "description": "The name of the product"
      },
      "price": {
        "type": "number",
        "description": "The price of the product in USD"
      },
      "category": {
        "type": "string",
        "description": "The product category"
      },
      "features": {
        "type": "array",
        "description": "List of product features",
        "items": {
          "type": "string"
        }
      },
      "inStock": {
        "type": "boolean",
        "description": "Whether the product is in stock"
      }
    }
  }
}

 

Step 5: Using JSON Mode with OpenAI Models

 

If your model supports JSON mode (available in newer OpenAI models), use it:

  • In the OpenAI node, find the "Response Format" or similar setting
  • Set it to "JSON" if available
  • This tells the model to strictly output valid JSON

For example, with the OpenAI API you would use:


{
  "model": "gpt-4-turbo",
  "response_format": { "type": "json_object" },
  "messages": [
    {"role": "system", "content": "You are a helpful assistant designed to output JSON."},
    {"role": "user", "content": "Generate product data for a smartphone"}
  ]
}

 

Step 6: Crafting Effective Prompts for JSON Generation

 

When function calling or JSON mode isn't available, proper prompt engineering becomes crucial:

  • Be explicit about the required JSON structure
  • Include examples of the expected output format
  • Use system messages to enforce JSON-only responses

Here's an example of an effective prompt:


System: You are a JSON generation assistant. You must ONLY output valid JSON without any additional text, explanations, or markdown formatting. Do not include `json or ` markers.

User: Generate a product object with these fields:
- name (string)
- price (number)
- category (string)
- features (array of strings)
- inStock (boolean)

The response must be a valid JSON object with exactly these fields and appropriate data types.

 

Step 7: Implementing JSON Validation and Parsing

 

Even with the best prompting, validation is essential. Add a Function node after your LLM node:

  • Click the "+" to add a new node after your OpenAI node
  • Select "Function" node
  • Implement JSON validation and parsing logic

// Function Node Code
function extractAndValidateJSON(input) {
  let text = input;
  
  // If the response might contain markdown code blocks
  if (typeof text === 'string' && (text.includes('`json') || text.includes('`'))) {
    // Extract JSON from code blocks
    const match = text.match(/`(?:json)?\s*([\s\S]*?)\s*`/);
    if (match && match[1]) {
      text = match[1].trim();
    }
  }
  
  // Try to parse the JSON
  try {
    // Check if it's already a JSON object
    if (typeof text === 'object' && text !== null) {
      return text;
    }
    
    // Otherwise parse string to JSON
    const parsedJSON = JSON.parse(text);
    return parsedJSON;
  } catch (error) {
    // Handle parsing errors
    throw new Error(`Failed to parse JSON: ${error.message}. Original text: ${text}`);
  }
}

// Get the LLM response
const llmResponse = $input.item.json.response || $input.item.json.content || $input.item.json;

// Process the response
try {
  const validJSON = extractAndValidateJSON(llmResponse);
  return {json: validJSON};
} catch (error) {
  return {json: {error: error.message, originalResponse: llmResponse}};
}

 

Step 8: Adding a Retry Mechanism for Malformed JSON

 

When the language model generates invalid JSON, implement automatic retries:

  • Add an "IF" node after the Function node
  • Configure it to check if the JSON validation failed
  • On failure, route back to the OpenAI node with an improved prompt

For the IF node, use a condition like:


// Check if error exists in the output
return !!$input.item.json.error;

Then, modify your retry prompt to be more explicit:


// Set parameters for retry
const originalPrompt = $node["OpenAI"].json.prompt;
const errorMessage = $input.item.json.error;

return {
  json: {
    prompt: \`I need ONLY valid JSON. The previous attempt failed with error: ${errorMessage}. 
    
Original request: ${originalPrompt}

Ensure your response contains ONLY a valid JSON object with no explanations, prefixes or suffixes. Do not use code blocks, just return the raw JSON.\`
  }
};

 

Step 9: Implementing JSON Schema Validation

 

For stricter validation against your expected schema:

  • Add another Function node for schema validation
  • Implement JSON Schema validation using a library like Ajv

// Function Node: JSON Schema Validation
// First, you'll need to install Ajv in n8n:
// n8n CLI: npm install ajv

const Ajv = require('ajv');
const ajv = new Ajv();

// Define your schema
const schema = {
  type: "object",
  required: ["name", "price", "category", "features", "inStock"],
  properties: {
    name: { type: "string" },
    price: { type: "number" },
    category: { type: "string" },
    features: { 
      type: "array",
      items: { type: "string" }
    },
    inStock: { type: "boolean" }
  }
};

// Get the parsed JSON
const parsedJSON = $input.item.json;

// Validate against schema
const validate = ajv.compile(schema);
const valid = validate(parsedJSON);

if (!valid) {
  return {
    json: {
      valid: false,
      errors: validate.errors,
      originalData: parsedJSON
    }
  };
} else {
  return {
    json: {
      valid: true,
      data: parsedJSON
    }
  };
}

 

Step 10: Creating a JSON Repair Function

 

Sometimes, the LLM output is almost valid JSON with minor issues. Add a repair function:


// Function to attempt repairing common JSON syntax errors
function attemptJSONRepair(text) {
  if (typeof text !== 'string') return text;
  
  let repairedText = text;
  
  // Trim whitespace and remove any surrounding text
  repairedText = repairedText.trim();
  
  // Try to find JSON-like content if surrounded by other text
  const jsonMatch = repairedText.match(/({[\s\S]_}|[[\s\S]_])/);
  if (jsonMatch) {
    repairedText = jsonMatch[0];
  }
  
  // Fix unquoted property names
  repairedText = repairedText.replace(/(\s_)(\w+)(\s_):(\s\*)/g, '$1"$2"$3:$4');
  
  // Fix trailing commas in objects and arrays
  repairedText = repairedText.replace(/,(\s\*[}]])/g, '$1');
  
  // Fix missing quotes around string values
  // This is a simplified approach and might not catch all cases
  repairedText = repairedText.replace(/:(\s\*)(?![{[\d"'\`true|false|null])([^,}]]+)/g, ': "$2"');
  
  // Replace single quotes with double quotes
  repairedText = repairedText.replace(/'/g, '"');
  
  try {
    return JSON.parse(repairedText);
  } catch (e) {
    // If repair attempt failed, return null
    return null;
  }
}

// Main function to process input
const input = $input.item.json.originalResponse;

// Try to repair if it's a string
const repairedJSON = attemptJSONRepair(input);

if (repairedJSON) {
  return { json: { success: true, repairedJSON } };
} else {
  return { json: { success: false, message: "Could not repair JSON" } };
}

 

Step 11: Implementing a Template-Based Approach

 

For maximum reliability, consider a template-based approach:

  • Have the LLM generate values for specific fields
  • Use a Function node to insert these values into a predefined JSON template

// Function to extract specific fields and build JSON
function buildJSONFromTemplate(llmResponse) {
  // Define extraction patterns for each field
  const patterns = {
    name: /Product Name:\s\*([^\n]+)/i,
    price: /Price:\s_$?(\d+.?\d_)/i,
    category: /Category:\s\*([^\n]+)/i,
    features: /Features:\s\*([\s\S]+?)(?=\n\w+:|$)/i,
    inStock: /In Stock:\s\*(yes|true|no|false)/i
  };
  
  // Extract values
  const result = {};
  
  for (const [key, pattern] of Object.entries(patterns)) {
    const match = llmResponse.match(pattern);
    if (match) {
      if (key === 'features') {
        // Parse features as array
        const featuresText = match[1];
        result[key] = featuresText
          .split('\n')
          .map(f => f.replace(/^-\s\*/, '').trim())
          .filter(f => f.length > 0);
      } else if (key === 'price') {
        // Convert price to number
        result[key] = parseFloat(match[1]);
      } else if (key === 'inStock') {
        // Convert to boolean
        result[key] = /yes|true/i.test(match[1]);
      } else {
        result[key] = match[1].trim();
      }
    } else {
      // Default values if not found
      if (key === 'features') result[key] = [];
      else if (key === 'price') result[key] = 0;
      else if (key === 'inStock') result[key] = false;
      else result[key] = "";
    }
  }
  
  return result;
}

// Process LLM response
const llmText = $input.item.json.response || $input.item.json.content || $input.item.json;
const structuredData = buildJSONFromTemplate(llmText);

return { json: structuredData };

 

Step 12: Setting Up a Dedicated JSON Formatter Node

 

Create a reusable subworkflow for JSON formatting:

  • Create a new workflow named "JSON Formatter"
  • Add input nodes to receive the LLM output
  • Implement the JSON extraction, validation, and repair logic
  • Save this workflow and use it as a subworkflow in your main workflows

 

Step 13: Using Multiple Language Models for Verification

 

For critical applications, implement a verification system:

  • Send the same request to multiple language model providers
  • Compare the outputs for consistency
  • Use a voting mechanism to select the most reliable response

Set up parallel nodes for different providers (OpenAI, Anthropic, etc.) and then add a Function node to compare results:


// Function to compare JSON outputs from multiple LLMs
function compareJSONOutputs(outputs) {
  // Ensure all outputs are parsed JSON
  const parsedOutputs = outputs.map(output => {
    if (typeof output === 'string') {
      try {
        return JSON.parse(output);
      } catch (e) {
        return null;
      }
    }
    return output;
  }).filter(output => output !== null);
  
  if (parsedOutputs.length === 0) {
    return { error: "No valid JSON outputs" };
  }
  
  // If only one valid output, return it
  if (parsedOutputs.length === 1) {
    return parsedOutputs[0];
  }
  
  // Compare structures and select most consistent
  // This is a simplified approach - you might need more complex logic
  const fields = {};
  
  // Count occurrences of each field
  parsedOutputs.forEach(output => {
    Object.keys(output).forEach(key => {
      if (!fields[key]) fields[key] = 0;
      fields[key]++;
    });
  });
  
  // Find most common fields (appearing in majority of outputs)
  const threshold = Math.ceil(parsedOutputs.length / 2);
  const commonFields = Object.keys(fields).filter(key => fields[key] >= threshold);
  
  // Build result using most common fields
  const result = {};
  commonFields.forEach(field => {
    // Get all values for this field
    const values = parsedOutputs
      .filter(output => output[field] !== undefined)
      .map(output => output[field]);
    
    // For simple types, use the most common value
    if (typeof values[0] !== 'object') {
      const valueCounts = {};
      values.forEach(val => {
        const valueStr = String(val);
        if (!valueCounts[valueStr]) valueCounts[valueStr] = 0;
        valueCounts[valueStr]++;
      });
      
      let maxCount = 0;
      let mostCommonValue = values[0];
      
      Object.entries(valueCounts).forEach(([val, count]) => {
        if (count > maxCount) {
          maxCount = count;
          mostCommonValue = val;
        }
      });
      
      // Convert back to original type
      if (typeof values[0] === 'number') {
        result[field] = Number(mostCommonValue);
      } else if (typeof values[0] === 'boolean') {
        result[field] = mostCommonValue === 'true';
      } else {
        result[field] = mostCommonValue;
      }
    } else {
      // For objects/arrays, use the first one (simplistic approach)
      result[field] = values[0];
    }
  });
  
  return result;
}

// Get outputs from different LLM nodes
const outputs = [
  $('OpenAI').item.json,
  $('Anthropic').item.json,
  $('GoogleAI').item.json
];

// Compare and return most consistent output
const result = compareJSONOutputs(outputs);
return { json: result };

 

Step 14: Implementing a Structured Output Caching System

 

To improve consistency across runs, implement caching:

  • Add a Cache node in n8n or use a Function node with persistent storage
  • Store successful JSON outputs keyed by prompts
  • Check the cache before calling the language model

// Simple caching system using n8n persistent variables
// First, check if we have a cached result

// Generate a cache key from the prompt
const prompt = $input.item.json.prompt;
const cacheKey = `json_cache_${Buffer.from(prompt).toString('base64').substring(0, 32)}`;

// Try to get cached result
let cachedResult;
try {
  cachedResult = await $node.context.getWorkflowStaticData('global')[cacheKey];
} catch (error) {
  cachedResult = null;
}

// If we have a valid cached result, return it
if (cachedResult) {
  return { 
    json: { 
      result: cachedResult,
      source: 'cache'
    } 
  };
}

// If no cache hit, proceed with calling the LLM
// The result from the LLM would be processed in a subsequent node
return { 
  json: { 
    prompt: prompt,
    cacheKey: cacheKey
  } 
};

Then, in a node after your LLM call:


// Store successful result in cache
const result = $input.item.json;
const cacheKey = $('PreviousNode').item.json.cacheKey;

// Only cache if we have valid JSON
if (result && !result.error) {
  // Store in workflow static data
  await $node.context.getWorkflowStaticData('global')[cacheKey] = result;
}

return { json: result };

 

Step 15: Fine-Tuning a Model for JSON Generation

 

For the most reliable results, consider fine-tuning a model specifically for JSON generation:

  • Create a dataset of prompt-JSON pairs specific to your use case
  • Use OpenAI's fine-tuning API or another provider's fine-tuning capabilities
  • Integrate the fine-tuned model in your n8n workflow

This requires preparation outside of n8n, but the implementation would be similar to the standard OpenAI node with your custom model specified.

 

Step 16: Monitoring and Logging JSON Generation Quality

 

Set up monitoring to track the quality of JSON generation:

  • Add logging nodes to track success and failure rates
  • Implement metrics to measure JSON validity rates
  • Set up alerts for high failure rates

// Function node for logging JSON generation quality
const result = $input.item.json;
const isValid = result && !result.error;
const timestamp = new Date().toISOString();

// Prepare log entry
const logEntry = {
  timestamp: timestamp,
  prompt: $('PromptNode').item.json.prompt,
  success: isValid,
  error: isValid ? null : result.error,
  model: $('LLMNode').parameter.model || 'unknown'
};

// Store in n8n storage or send to external logging system
let logs = await $node.context.getWorkflowStaticData('global').jsonGenLogs || [];
logs.push(logEntry);

// Keep only last 100 entries
if (logs.length > 100) {
  logs = logs.slice(-100);
}

await $node.context.getWorkflowStaticData('global').jsonGenLogs = logs;

// Calculate success rate
const successRate = logs.filter(log => log.success).length / logs.length;

// Alert if success rate drops below threshold
if (successRate < 0.8 && logs.length >= 10) {
  // Trigger alert (e.g., via email, Slack, etc.)
  // This would connect to another node in your workflow
}

// Return original result along with metrics
return {
  json: {
    ...result,
    metrics: {
      successRate: successRate,
      sampleSize: logs.length
    }
  }
};

 

Step 17: Putting It All Together: A Complete Workflow

 

Now let's build a complete n8n workflow combining these techniques:

  1. Start node: Initiates the workflow with a prompt
  2. Cache Check: Checks if we have a cached result for this prompt
  3. OpenAI Function Call: Calls OpenAI with function calling enabled
  4. JSON Validation: Validates the returned JSON
  5. Error Handling: If validation fails, retries with improved prompting
  6. JSON Repair: Attempts to fix minor JSON issues
  7. Schema Validation: Ensures the JSON matches our expected schema
  8. Caching: Stores successful results for future use
  9. Metrics: Logs success/failure for monitoring
  10. Output: Returns the validated JSON

This approach combines multiple techniques for maximum reliability and gives you a robust system for consistent JSON generation from language models in n8n.

 

Step 18: Handling Special Data Types and Nested Structures

 

For complex JSON with special data types or nested structures:

  • Use explicit type hints in your prompts
  • Provide complete examples with the desired nesting structure
  • Implement custom validation for special types (dates, URLs, etc.)

// Function to validate and fix special data types
function validateAndFixTypes(json, schema) {
  // Deep copy to avoid modifying the original
  const result = JSON.parse(JSON.stringify(json));
  
  // Helper function to process a value according to a schema
  function processValue(value, schema) {
    if (!schema) return value;
    
    // Handle different types
    switch (schema.type) {
      case 'string':
        // Convert to string if needed
        return String(value);
        
      case 'number':
        // Convert to number if possible
        const num = Number(value);
        return isNaN(num) ? 0 : num;
        
      case 'boolean':
        // Handle various boolean representations
        if (typeof value === 'string') {
          return /^(true|yes|1)$/i.test(value);
        }
        return Boolean(value);
        
      case 'array':
        // Ensure it's an array
        if (!Array.isArray(value)) {
          value = [value].filter(v => v !== undefined && v !== null);
        }
        // Process array items if schema is provided
        if (schema.items) {
          return value.map(item => processValue(item, schema.items));
        }
        return value;
        
      case 'object':
        // Ensure it's an object
        if (typeof value !== 'object' || value === null || Array.isArray(value)) {
          return schema.properties ? {} : {};
        }
        // Process object properties
        if (schema.properties) {
          const result = {};
          Object.keys(schema.properties).forEach(key => {
            result[key] = processValue(value[key], schema.properties[key]);
          });
          return result;
        }
        return value;
        
      default:
        return value;
    }
  }
  
  // Process the entire object
  return processValue(result, schema);
}

// Get the parsed JSON and schema
const parsedJSON = $input.item.json;
const schema = {
  type: "object",
  properties: {
    name: { type: "string" },
    price: { type: "number" },
    createdAt: { type: "string" }, // Could be a date string
    categories: { 
      type: "array",
      items: { type: "string" }
    },
    details: {
      type: "object",
      properties: {
        manufacturer: { type: "string" },
        yearReleased: { type: "number" },
        specifications: {
          type: "array",
          items: { 
            type: "object",
            properties: {
              name: { type: "string" },
              value: { type: "string" }
            }
          }
        }
      }
    },
    inStock: { type: "boolean" }
  }
};

// Fix types according to schema
const fixedJSON = validateAndFixTypes(parsedJSON, schema);

return { json: fixedJSON };

 

Step 19: Optimizing for Performance and Cost

 

When dealing with large volumes of JSON generation:

  • Implement batching for multiple JSON objects
  • Optimize prompt length to reduce token usage
  • Set up fallback to simpler models for basic JSON generation

// Function to optimize prompt for JSON generation
function optimizePrompt(originalPrompt, complexity) {
  // Base system prompt for JSON generation
  const baseSystemPrompt = "Generate valid JSON only. No explanations or additional text.";
  
  // Classify complexity
  let model, temperature, maxTokens;
  
  if (complexity === 'simple') {
    // Simple JSON structures can use faster/cheaper models
    model = "gpt-3.5-turbo";
    temperature = 0.1;
    maxTokens = 500;
  } else if (complexity === 'medium') {
    model = "gpt-3.5-turbo";
    temperature = 0.2;
    maxTokens = 1000;
  } else {
    // Complex JSON needs more capable models
    model = "gpt-4";
    temperature = 0.3;
    maxTokens = 2000;
  }
  
  // Streamline the prompt to reduce tokens
  let streamlinedPrompt = originalPrompt;
  
  // Remove unnecessary explanations
  streamlinedPrompt = streamlinedPrompt.replace(/please |kindly |I would like |I need |generate for me /gi, '');
  
  // Simplify instructions
  streamlinedPrompt = `Generate JSON: ${streamlinedPrompt}`;
  
  return {
    model,
    temperature,
    maxTokens,
    systemPrompt: baseSystemPrompt,
    userPrompt: streamlinedPrompt
  };
}

// Get the original prompt and estimate complexity
const originalPrompt = $input.item.json.prompt;

// Simple complexity detection (could be more sophisticated)
let complexity = 'simple';
if (originalPrompt.includes('nested') || originalPrompt.includes('complex') || 
    originalPrompt.length > 200) {
  complexity = 'complex';
} else if (originalPrompt.length > 100) {
  complexity = 'medium';
}

// Optimize the prompt
const optimized = optimizePrompt(originalPrompt, complexity);

// Return optimized parameters
return {
  json: {
    prompt: optimized.userPrompt,
    systemPrompt: optimized.systemPrompt,
    model: optimized.model,
    temperature: optimized.temperature,
    maxTokens: optimized.maxTokens
  }
};

 

Step 20: Implementing Security Best Practices

 

Ensure security when generating and handling JSON:

  • Sanitize prompts to prevent prompt injection
  • Validate JSON outputs for security issues (e.g., prototype pollution)
  • Implement access controls for sensitive JSON data

// Function to sanitize prompts and check for security issues
function secureJSONGeneration(prompt, jsonOutput) {
  // 1. Sanitize the prompt
  const sanitizedPrompt = sanitizePrompt(prompt);
  
  // 2. Check JSON output for security issues
  const securityCheck = checkJSONSecurity(jsonOutput);
  
  return {
    sanitizedPrompt,
    securityCheck
  };
}

// Sanitize prompt to prevent injection
function sanitizePrompt(prompt) {
  if (typeof prompt !== 'string') return '';
  
  // Remove potential instruction hijacking patterns
  let sanitized = prompt.replace(/ignore previous instructions|disregard|instead of|forget/gi, '[FILTERED]');
  
  // Limit prompt length
  sanitized = sanitized.slice(0, 1000);
  
  // Remove special characters that might be used for injection
  sanitized = sanitized.replace(/[^\w\s.,?!:;()[]{}'"/-+]/g, '');
  
  return sanitized;
}

// Check JSON for security issues
function checkJSONSecurity(json) {
  const issues = [];
  
  // Deep scan the JSON object
  function scanObject(obj, path = '') {
    if (!obj || typeof obj !== 'object') return;
    
    // Check for prototype pollution attempts
    if (obj.hasOwnProperty('**proto**') || obj.hasOwnProperty('constructor') || 
        obj.hasOwnProperty('prototype')) {
      issues.push(`Potential prototype pollution at ${path}`);
    }
    
    // Check for suspicious property names
    Object.keys(obj).forEach(key => {
      // Check for extremely long keys
      if (key.length > 100) {
        issues.push(`Suspicious long property name at ${path}.${key}`);
      }
      
      // Check for nested objects and arrays
      const value = obj[key];
      if (value && typeof value === 'object') {
        scanObject(value, path ? `${path}.${key}` : key);
      }
      
      // Check for code injection in strings
      if (typeof value === 'string') {
        if (value.includes('function(') || value.includes('=>') || 
            value.includes('eval(') || value.includes('new Function')) {
          issues.push(`Potential code injection at ${path}.${key}`);
        }
      }
    });
  }
  
  scanObject(json);
  
  return {
    safe: issues.length === 0,
    issues
  };
}

// Process inputs
const prompt = $input.item.json.prompt;
const jsonOutput = $input.item.json.result;

const security = secureJSONGeneration(prompt, jsonOutput);

// If there are security issues, sanitize the output
let finalOutput = jsonOutput;
if (!security.securityCheck.safe) {
  // Log security issues
  console.log(`Security issues found: ${security.securityCheck.issues.join(', ')}`);
  
  // Create a sanitized version (simple approach - in production you might want more sophisticated sanitization)
  finalOutput = JSON.parse(JSON.stringify(jsonOutput, (key, value) => {
    // Remove suspicious keys
    if (key === '**proto**' || key === 'constructor' || key === 'prototype') {
      return undefined;
    }
    // Sanitize strings
    if (typeof value === 'string') {
      return value.replace(/function(|=>|eval(|new Function/g, '[FILTERED]');
    }
    return value;
  }));
}

return {
  json: {
    original: jsonOutput,
    sanitized: finalOutput,
    securityIssues: security.securityCheck.issues,
    sanitizedPrompt: security.sanitizedPrompt
  }
};

This comprehensive guide covers everything you need to get consistent JSON outputs from language models in n8n. By combining prompt engineering, function calling, validation, error handling, and the other techniques described, you can build robust workflows that reliably generate and process structured JSON data from even the most unpredictable language models.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022