Come gestire json.decoder.JSONDecodeError quando un'API restituisce risposta vuota in Python?

👤 Iniziato da @proserpinagatti34
📅 13/07/2025 11:30
📁 Programmazione 🌐 IT
Avatar di proserpinagatti34
Ragazzi, sto sviluppando un programmino per organizzare le mie degustazioni di vino con gli amici, e mi sono bloccata con un errore JSON. Quando chiamo un'API di ricette enologiche (usando Python 3.10 e la libreria requests), se la risposta è vuota, ricevo questo errore:

```python
import requests
response = requests.get('https://api.ricette-vino.com/abbinamenti')
dati = response.json() # Errore qui se response vuoto
```

L'errore esatto è:
```
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```
Ho già verificato che a volte l'API torna status 200 ma con body completamente vuoto, e so che response.text restituisce una stringa vuota. Ho provato a usare un try-except, ma mi chiedo: qual è il modo più elegante per gestire questi casi? Forse controllare len(response.content) prima? O esiste un parametro in requests per evitarlo? Se avete affrontato situazioni simili con API ballerine, soprattutto in progetti gastronomici, condividete i vostri trucchi!
Avatar di bluamato10
Ecco, le API che tornano 200 con corpo vuoto sono una delle cose che mi fanno bestemmiare in tre lingue. Ti capisco benissimo!

Il try-except è la strada giusta, ma possiamo renderlo più pulito. Io di solito faccio così:

```python
response = requests.get('https://api.ricette-vino.com/abbinamenti')
try:
dati = response.json() if response.content else None
except ValueError: # cattura sia JSONDecodeError che altri errori di parsing
dati = None
```

Oppure, se preferisci essere esplicito:

```python
if response.status_code == 200 and response.text.strip():
dati = response.json()
else:
dati = None # o un dizionario vuoto {}, dipende da come vuoi gestirlo
```

Però occhio: controllare solo `len(response.content)` può essere rischioso se l’API torna spazi bianchi o porcherie simili. `response.text.strip()` è più sicuro.

Ah, e se lavori spesso con API fatte male, valuta di aggiungere un timeout alle request, perché quelle che tornano 200 a vuoto a volte sono anche lentissime.

P.S. Ottima scelta organizzare degustazioni, io sul vino sono un disastro, finirei a bere tutto il barolo al primo giro!
Avatar di ernestosala
Quando lavori con API instabili, devi mettere in conto che a volte ti rispondano con contenuti indecifrabili. Il problema qui è che `response.json()` prova a decodificare qualunque cosa trovi, anche stringhe vuote o malformate. Io aggiungerei un controllo più granulare prima di tentare il parsing:

```python
response = requests.get('https://api.ricette-vino.com/abbinamenti', timeout=5)
if response.status_code == 200 and response.text.strip().startswith('{'):
dati = response.json()
else:
dati = {} # o [] se ti aspetti una lista
```

Così filtri solo i casi in cui il contenuto abbia effettivamente una struttura JSON (iniziando con `{`). Però occhio: se l’API restituisce dati validi ma occasionalmente vuoti (es. `{}`), quel controllo escluderebbe risposte legittime. In quel caso, meglio gestire separatamente i due scenari:

```python
try:
dati = response.json() if response.text.strip() else {"errore": "nessun dato"}
except json.JSONDecodeError:
dati = {"errore": "json non valido"}
```

E, come detto, il timeout è un must. Ho avuto casi in cui l’API rispondevva dopo 30 secondi con un body vuoto… non far sprecare tempo al programma per errori server.
Avatar di gabriellalombardo80
Madonna, ste API che ti fanno il regalo del 200 con il body vuoto... un classico che ti fa venire voglia di lanciare la tastiera! Concordo con @bluamato10 sul controllo di `response.text.strip()`, ma evita l'approccio di @ernestosala di controllare `startswith('{')`: se un giorno l'API inizia a restituire array (come `[]`), il codice esplode.

La mia soluzione preferita? Un ibrido efficienza-eleganza:

```python
response = requests.get(url, timeout=3)
try:
dati = response.json() if response.content else {}
except json.JSONDecodeError:
dati = {} # logga l'errore se serve
```

Spiegazione lampo:
- `timeout` previene attese infinite (API vinicole a volte son lente come trattori)
- `response.content` è più veloce di `response.text.strip()` per verificare il vuoto
- Il `try-except` cattura JSON malformati (es. "null" o virgole pazze)
- Assegni un dizionario vuoto per evitare crash nel codice successivo

Se l'API è particolarmente ballerina, crea una funzione wrapper che gestisca questi casi sporchi. Così quando organizzi le tue degustazioni, almeno il codice non ti rovina la serata!
Avatar di gaiapellegrini
Mi sa che stai brancolando nel buio col rischio di romperti il naso, ma ci sto passando anch'io con API inaffidabili. Quel `JSONDecodeError` quando ti sparano un 200 vuoto è una presa per il culo bella e buona.

La soluzione meno farraginosa? Copia spudoratamente l'approccio di @gabriellalombardo80, ma con due aggiustamenti da chi ci è già scottato:

```python
try:
response = requests.get(url, timeout=5)
response.raise_for_status() # Aggiungi QUESTA per catturare 400/500 mascherati
dati = response.json() if response.content else None
except (requests.exceptions.RequestException, json.JSONDecodeError):
dati = None # o fallback a un dizionario vuoto se preferisci
```

Perché funziona meglio:
- `raise_for_status()` blocca subito i codici HTTP falsi (404, 500, ecc.) che a volte quelle API vinicole ti rifilano come se niente fosse
- Controllare `response.content` è più rapido di `text.strip()` e ti evita rogne con encoding
- Il doppio except piglia sia errori di rete che JSON zoppi

Se poi vuoi essere paranoico, aggiungi un logger nell'except per tracciare gli URL che ti fanno il dispetto. E per l'amor del vino, usa sempre il timeout: le API enologiche hanno la reattività di una bottiglia di Barolo '82 stappata ieri.
Avatar di proserpinagatti34
Grazie @gaiapellegrini, hai centrato proprio il mio dilemma! Il paragone col Barolo '82 è spietatamente vero 😂 Apprezzo tantissimo i tuoi aggiustamenti:

- **raise_for_status()** è oro, non mi ero accorta che certe API vinicole nascondono errori dietro falsi 200
- **response.content** invece del text.strip() è una svolta per l'encoding (ho già avuto rogne con caratteri strani nelle schede tecniche dei vini)
- Il **doppio except** è quel che cercavo: semplice ma copre tutto, dai timeout ai JSON corrotti

Manderò in produzione con il logger come suggerisci: traccerò gli URL "bugiardi" mentre sorseggio un buon rosso! Direi che il codice è pronto per la prossima degustazione 🍷
Avatar di massimocosta
Ottimo lavoro @proserpinagatti34, la soluzione con `raise_for_status()` e il doppio except è solida come una barrique di Brunello. Solo due appunti dal mio vissuto con API "creative":

1. **Logging differenziato**:
```python
except requests.exceptions.RequestException as e:
logger.error(f"Errore rete: {e} - URL: {url}")
except json.JSONDecodeError:
logger.warning(f"JSON malformato - URL: {url} - Contenuto: {response.content[:50]}")
```
Così distingui i problemi di rete da quelli dei dati, fondamentale se usi provider diversi.

2. **Fallback intelligente**:
Invece di `dati = None`, potresti avere un dizionario predefinito con suggerimenti d'abbinamento di riserva tipo {"consiglio": "Prova con un Sangiovese secco"}. Così l'app non si blocca e gli amici hanno comunque un'idea.

Sul vino... quel Barolo '82 è un'ottima metafora: come certe API, anche i grandi rossi a volte hanno sedimenti da filtrare 😉. Se incontri altri bug, fai come col vino giovane: lascialo ossigenare prima di debuggarci.
Avatar di domenicazanella14
Hai ragione su tutto, Max! Il **logger differenziato** è un must quando l’API ti risponde come un sommelier ubriaco: errori di rete sono rogne fisiche, JSON vuoti sono tradimenti spirituali. Separarli? Meglio che dividere la diramazione del Nebbiolo dal suo sedimente.

Sul **fallback intelligente**, poi, non hai sbagliato un colpo. L’utente vuole un consiglio, non un crash. Un dizionario di riserva con abbinamenti tipo Sangiovese o Aglianico è come tenere una bottiglia di emergenza in cantina: non elegante, ma salva la serata.

Io aggiungerei un dettaglio: dopo il fallback, magari notifica con un logger.warning che stai usando la riserva. Così chi usa l’app sa che il consiglio è “autoprodotto”, non ufficiale. E se gli amici protestano, rispondi: “Era o il vino o il debug”.

P.S. Sul Barolo ’82 hai centrato il paradosso: certe API sono come i grandi rossi, ti fanno innamorare ma poi ti costringono a filtrare notti intere. Buona vendemmia, e se vedi un Chianti con pH 3.0… buttalo via. 🍷

La Tua Risposta

💬

Vuoi partecipare alla discussione?

Accedi o registrati per scrivere la tua risposta e unirti alla conversazione!