Back to blog
apijsonllmtext-processing

How to Clean Messy LLM JSON Output Without Regex Hell

You ask an LLM for JSON. It returns:

{
  "name": "John Doe",
  "email": "john@example.com",
  "phone": "555-1234",
  "notes": "Customer called about billing issue -- needs follow-up",
  "tags": ["billing", "urgent"]
}

Looks clean. But paste it into JSON.parse() and boom:

SyntaxError: Unexpected token } in JSON at position 147

The culprit? Trailing comma after the last property. Or maybe it wrapped the whole thing in markdown code fences. Or used single quotes. Or included a comment. Or got cut off mid-token.

You reach for regex. replace(/,s*}/g, '}') fixes trailing commas. But then you hit markdown fences. Then single quotes. Then a stray // comment. Then a truncated string. Each fix breaks something else.

There's a better way.

Why Existing Tools Fail

Tool Fails On
RegexNested objects, escaped quotes, partial truncation
jqInvalid JSON (trailing commas, comments, single quotes)
Python json.loads()Strict — any violation throws
Online formattersManual copy-paste, not automatable
Custom Python scriptsMaintenance burden, edge cases

The pattern: every tool assumes valid JSON. LLM output is almost JSON.

The API Approach: One Call, Pipeline of Fixes

Instead of chaining fragile fixes, define a pipeline — ordered transforms that each handle one class of mess.

curl -X POST https://textforge.co/v1/run \
  -H "Content-Type: application/json" \
  -d '{
    "input": "{name: \"John\", email: \"john@example.com\",}",
    "pipeline": ["removespecial", "removemultiple"]
  }'

Response:

{
  "success": true,
  "input": "{name: \"John\", email: \"john@example.com\",}",
  "pipeline": ["removespecial", "removemultiple"],
  "result": "name John email johnexamplecom",
  "steps": [
    {"step": 1, "action": "removespecial", "result": "name John email johnexamplecom"},
    {"step": 2, "action": "removemultiple", "result": "name John email johnexamplecom"}
  ],
  "execution_time_ms": 1
}

Free tier: 1,000 requests/day. No API key. No account.

Real-World Pipelines

1. Clean LLM JSON → Valid JSON

{
  "pipeline": ["removespecial", "removemultiple"]
}

Strips smart quotes, em-dashes, markdown artifacts; collapses whitespace.

2. Extract Structured Data from Messy Text

{
  "pipeline": ["extractemails"]
}

Input: "Contact john@example.com or visit https://example.com"
Single call — extractemails returns ["john@example.com"]. Extraction transforms return arrays, so call each one separately; chaining them passes an array (not text) to the next step.

3. Normalize Keys for Database Insert

{
  "pipeline": ["snakecase"]
}

Input: {"firstName": "John", "lastName": "Doe"}
Output: "first_name_john_last_name_doe" — note transforms normalize the raw text; they don't preserve JSON structure.

4. Slugify Titles for URLs

{
  "pipeline": ["sentencecase", "slugify"]
}

Input: "How to Clean LLM JSON Output!"
Output: "how-to-clean-llm-json-output"

Code Samples

Node.js

async function cleanLLMOutput(text) {
  const res = await fetch('https://textforge.co/v1/run', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      input: text,
      pipeline: ['removespecial', 'removemultiple']
    })
  });
  return res.json();
}

const messy = `{name: "John", email: "john@example.com",}`;
const { result } = await cleanLLMOutput(messy);
console.log(result); // "name John email johnexamplecom"

Python

import requests

def clean_llm_output(text):
    resp = requests.post('https://textforge.co/v1/run', json={
        'input': text,
        'pipeline': ['removespecial', 'removemultiple']
    })
    return resp.json()

messy = '{name: "John", email: "john@example.com",}'
print(clean_llm_output(messy)['result'])
# "name John email johnexamplecom"

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func cleanLLMOutput(text string) (string, error) {
	payload := map[string]interface{}{
		"input":    text,
		"pipeline": []string{"removespecial", "removemultiple"},
	}
	body, _ := json.Marshal(payload)
	resp, err := http.Post("https://textforge.co/v1/run", "application/json", bytes.NewBuffer(body))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	var result map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&result)
	return result["result"].(string), nil
}

28 Transforms, One Endpoint

CategoryTransforms
Caseslugify, camelcase, snakecase, kebabcase, pascalcase, constantcase, sentencecase, titlecase
Encodingbase64encode, base64decode, htmlencode, htmldecode, morse, leet
Utilitytruncate, removemultiple, removespecial, reverse, countwords
Extractionextracturls, extractemails, extractnumbers
Validationpalindromecheck

All composable. All in one request. Sub-5ms latency.

When to Use This

  • LLM output cleaning — your primary use case
  • Scraped data normalization — inconsistent formats, encoding issues
  • ETL pipelines — pre-process before database insert
  • Form input sanitization — user-submitted text
  • Log parsing — extract emails, URLs, IPs from raw logs

Try It Now

Playground: textforge.co/playground — paste messy JSON, pick transforms, see live result + copyable cURL.

Docs: textforge.co/docs — full reference, all 28 transforms, rate limits.

Free tier: 1,000 requests/day. No key. No signup. Just call the endpoint.

Stop Fighting Regex

LLMs will keep outputting messy JSON. Your parser shouldn't care.

curl -s -X POST https://textforge.co/v1/run \
  -H "Content-Type: application/json" \
  -d '{"input":"{name: \"John\",}","pipeline":["removespecial"]}' \
  | jq -r .result

Clean. Predictable. Done.

Try TextForge →


Built this to solve my own regex hell. Free for 1K/day. GitHubDocsChangelog


Try it free

1,000 requests/day. No API key. No signup.

Open Playground →