Smart_SEO_Media
EN
8 min lettura SEO Tecnico

JSON-LD malformato: come trovarlo e fixarlo prima che Google lo ignori

Una virgola di troppo alla fine di un array e Google salta l'intero blocco di schema markup. Nessun errore visibile, nessun warning in console. Solo rich results che non appaiono mai.

Durante l'audit di smartweb-media.com, uno script Python ha trovato un JSON-LD non valido su blog.html. Il sito lo serviva da settimane. Google lo ignorava silenziosamente da altrettante settimane.

Il problema: una trailing comma alla fine dell'ultimo elemento nell'array blogPost. In JavaScript questo è spesso accettato dai parser moderni. In JSON puro — che è lo standard che Google usa — è un errore sintattico fatale.

Ti mostro il problema, come trovarlo su tutto il sito automaticamente, e come fixarlo in modo che non si ripeta.

Il caso reale: trailing comma su blog.html

Ecco il JSON-LD problematico trovato su blog.html:

❌ JSON-LD malformato — blog.html

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Blog",
  "name": "Smart SEO Media Blog",
  "blogPost": [
    {
      "@type": "BlogPosting",
      "headline": "Perché il tuo traffico non converte?",
      "url": "https://smartweb-media.com/blog/traffico-non-converte.html",
      "datePublished": "2024-01-15"
    },
    {
      "@type": "BlogPosting",
      "headline": "SEO nel 2026: L'AI ha trasformato la ricerca?",
      "url": "https://smartweb-media.com/blog/seo-ai-2024.html",
      "datePublished": "2024-02-08"
    },    ← ERRORE: virgola dopo l'ultimo elemento
  ]         ← JSON standard non ammette trailing comma
}
</script>

✅ JSON-LD corretto — dopo il fix

    {
      "@type": "BlogPosting",
      "headline": "SEO nel 2026: L'AI ha trasformato la ricerca?",
      "url": "https://smartweb-media.com/blog/seo-ai-2024.html",
      "datePublished": "2024-02-08"
    }      ← nessuna virgola dopo l'ultimo elemento
  ]
}

La differenza è una singola virgola. L'impatto: Google non legge nessuno dei tuoi BlogPosting, nessun dato strutturato viene processato, nessun rich result viene generato.

Perché è difficile da accorgersi

Tre ragioni per cui questo errore rimane nascosto a lungo:

1. I browser non la segnalano

I browser moderni (Chrome, Firefox) hanno parser JSON lenient che accettano trailing comma. Aprire la pagina, ispezionare i tag script — nessun errore visibile nella console del browser.

2. La Search Console non invia alert immediati

Google Search Console segnala i problemi di schema solo nella sezione "Miglioramenti" e solo per tipo di schema. Se non ci sono mai stati rich results per quella pagina, non c'è nulla da confrontare — e nessun alert viene generato.

3. L'errore non rompe la pagina

La pagina funziona perfettamente. Gli utenti non vedono nulla di strano. Solo i crawler — Google, Bing, i parser di LinkedIn e Twitter — ricevono dati strutturati non validi e li scartano.

Script Python: audit JSON-LD su tutto il sito

Invece di controllare pagina per pagina nel Rich Results Test, ho scritto uno script che legge la sitemap, scarica ogni pagina e valida tutti i blocchi JSON-LD in uno shot:

audit_json_ld.py

import requests
import re
import json
import xml.etree.ElementTree as ET
from urllib.parse import urljoin

def fetch_page(url, timeout=10):
    """Scarica HTML di una pagina."""
    try:
        resp = requests.get(url, timeout=timeout,
                           headers={'User-Agent': 'Mozilla/5.0'})
        return resp.text if resp.status_code == 200 else None
    except Exception:
        return None

def extract_json_ld_blocks(html):
    """Estrae tutti i blocchi JSON-LD dall'HTML."""
    pattern = r']+type=["\']application/ld\+json["\'][^>]*>(.*?)'
    return re.findall(pattern, html, re.DOTALL | re.IGNORECASE)

def validate_json_ld(block):
    """Valida un singolo blocco JSON-LD."""
    try:
        data = json.loads(block.strip())
        schema_type = data.get('@type', 'Unknown') if isinstance(data, dict) else 'Array'
        return {'valid': True, 'type': schema_type, 'error': None}
    except json.JSONDecodeError as e:
        return {'valid': False, 'type': None, 'error': str(e)}

def find_common_errors(block):
    """Identifica errori comuni nel JSON-LD."""
    errors = []
    # Trailing comma prima di ] o }
    if re.search(r',\s*[\]\}]', block):
        errors.append('trailing_comma')
    # Apici singoli invece di doppi
    if re.search(r"'[^']+'\s*:", block):
        errors.append('single_quotes')
    # Proprietà senza virgolette
    if re.search(r'^\s*\w+\s*:', block, re.MULTILINE):
        errors.append('unquoted_keys')
    return errors

def audit_site_json_ld(sitemap_url):
    """Audita JSON-LD su tutto il sito dalla sitemap."""
    # Scarica sitemap
    resp = requests.get(sitemap_url, timeout=10)
    root = ET.fromstring(resp.content)
    ns = {'sm': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
    urls = [loc.text for loc in root.findall('.//sm:loc', ns)]

    print(f"Audit JSON-LD su {len(urls)} pagine\n{'='*60}")

    results = {'ok': [], 'errors': [], 'no_schema': []}

    for url in urls:
        html = fetch_page(url)
        if not html:
            print(f"  ⚠️  Impossibile scaricare: {url}")
            continue

        blocks = extract_json_ld_blocks(html)
        path = url.split('.com')[-1] or '/'

        if not blocks:
            results['no_schema'].append(url)
            print(f"  ○  {path:<50} nessun JSON-LD")
            continue

        page_ok = True
        for i, block in enumerate(blocks):
            validation = validate_json_ld(block)
            if validation['valid']:
                print(f"  ✅ {path:<50} schema #{i+1}: {validation['type']}")
                results['ok'].append({'url': url, 'type': validation['type']})
            else:
                common = find_common_errors(block)
                error_type = common[0] if common else 'syntax_error'
                print(f"  ❌ {path:<50} schema #{i+1}: {validation['error']}")
                print(f"     Tipo errore probabile: {error_type}")
                results['errors'].append({
                    'url': url,
                    'block_index': i + 1,
                    'error': validation['error'],
                    'error_type': error_type,
                    'block_preview': block.strip()[:200]
                })
                page_ok = False

    print(f"\n{'='*60}")
    print(f"RIEPILOGO:")
    print(f"  ✅ Schemi validi:    {len(results['ok'])}")
    print(f"  ❌ Errori trovati:   {len(results['errors'])}")
    print(f"  ○  Senza schema:     {len(results['no_schema'])}")

    if results['errors']:
        print(f"\nPAGINE DA FIXARE:")
        for err in results['errors']:
            print(f"  {err['url']}")
            print(f"    → {err['error_type']}: {err['error']}")

    return results

# Fix automatico per trailing comma
def fix_trailing_commas(html):
    """Rimuove trailing comma da tutti i blocchi JSON-LD nell'HTML."""
    def fix_block(match):
        block = match.group(1)
        # Rimuovi trailing comma prima di ] o }
        fixed = re.sub(r',(\s*[\]\}])', r'\1', block)
        return match.group(0).replace(block, fixed)

    pattern = r'(]+type=["\']application/ld\+json["\'][^>]*>)(.*?)()'
    return re.sub(pattern, fix_block, html, flags=re.DOTALL | re.IGNORECASE)

# Uso
if __name__ == '__main__':
    results = audit_site_json_ld('https://smartweb-media.com/sitemap.xml')

Gli errori JSON-LD più comuni

1. Trailing comma (il più frequente)

❌ "name": "Articolo 3",
]

✅ "name": "Articolo 3"
]

2. Apici singoli invece di doppi

❌ '@type': 'BlogPosting'

✅ "@type": "BlogPosting"

3. URL senza HTTPS

❌ "url": "http://tuosito.com/pagina"

✅ "url": "https://tuosito.com/pagina"

4. datePublished in formato non ISO 8601

❌ "datePublished": "30/03/2026"

✅ "datePublished": "2026-03-30"

5. @context mancante o sbagliato

❌ "@context": "http://schema.org"

✅ "@context": "https://schema.org"

Valida manualmente con il Rich Results Test

Per una verifica rapida senza script:

  1. Vai su search.google.com/test/rich-results
  2. Inserisci l'URL della pagina da verificare
  3. Google analizza i dati strutturati e mostra errori e warning
  4. Per ogni errore, clicca sul dettaglio per vedere il campo problematico

Lo svantaggio: devi farlo pagina per pagina. Per siti con 20+ pagine con schema markup, lo script Python è più efficiente.

Prevenire: JSON-LD generato programmaticamente

Il modo più sicuro per evitare errori JSON-LD è generarli programmaticamente invece di scriverli a mano nell'HTML:

genera_schema.py

import json

def generate_blog_schema(posts):
    """Genera JSON-LD Blog valido da una lista di post."""
    schema = {
        "@context": "https://schema.org",
        "@type": "Blog",
        "name": "Smart SEO Media Blog",
        "description": "Knowledge Base SEO: strategie e guide operative",
        "url": "https://smartweb-media.com/blog.html",
        "publisher": {
            "@type": "Organization",
            "name": "Smart SEO Media"
        },
        "blogPost": [
            {
                "@type": "BlogPosting",
                "headline": post['title'],
                "url": post['url'],
                "datePublished": post['date'],
                "author": {"@type": "Person", "name": "Antonio Montingelli"}
            }
            for post in posts
        ]
    }
    # json.dumps garantisce JSON valido — niente trailing comma mai
    return json.dumps(schema, ensure_ascii=False, indent=2)

# Uso
posts = [
    {"title": "Come uso Claude per fare audit SEO", "url": "https://smartweb-media.com/blog/seo-audit-con-claude-pipeline.html", "date": "2026-03-02"},
    {"title": "Search Console + Python: quick wins in 10 minuti", "url": "https://smartweb-media.com/blog/search-console-quick-wins.html", "date": "2026-03-30"}
]
schema_json = generate_blog_schema(posts)
print(schema_json)  # JSON sempre valido, zero rischio trailing comma

Hai schema markup sul tuo sito? Controlliamolo.

Giro lo script di audit JSON-LD su tutto il sito, trovo errori e warning, e ti consegno un report con i fix prioritizzati. Nessun cost aggiuntivo nell'audit gratuito.

Richiedi Audit Gratuito

Continua a leggere