Een AJAX-formulier verwacht JSON maar krijgt HTML: loop de respons stap voor stap na
Een 200-respons bewijst niet dat het JSON is. Loop het Netwerk-paneel stap voor stap na en scheid fouten in transport, weergave en contract.
Het formulier wordt verzonden, de spinner verschijnt en de console meldt een onverwacht token < in JSON op positie 0. De pagina zelf laadt prima, en daarom is de eerste reflex de parser of de tunnel de schuld geven. Meestal is geen van beide betrokken: het versturen is een ander verzoek dan het laden van de pagina, en er kwam iets terug dat geen JSON is. Werk in de volgorde waarin de browser het echt onthult, want de vier mogelijke antwoorden vragen vier verschillende reparaties.
Stap 1: leg het verzoek vast dat echt plaatsvond
Open het Netwerk-paneel voordat je verzendt, stel het logboek in om vermeldingen over navigaties heen te bewaren, en doe dan één onschuldige testinzending op een account dat je mag gebruiken. Kies het verzoek dat het formulier zelf startte, niet het documentverzoek van de pagina.
Wees voorzichtig met filters. Een XHR- of Fetch-filter is handig, maar als het framework nog steeds via een verborgen iframe of een dynamisch gemaakt formulier verstuurt, verbergt het filter het enige verzoek dat ertoe doet. Verzend eerst één keer zonder filter en kijk wat er verscheen.
Noteer voor het gekozen verzoek zes dingen voordat je iets interpreteert: de methode, de volledige URL inclusief de querystring, de status, de omleidingsketen, het Content-Type van de respons en de eerste regels van de body. De referentie voor het Netwerk-paneel van Chrome DevTools behandelt de weergaven Headers, Response en Initiator waar die staan, en de gelijkwaardige panelen in andere browsers tonen dezelfde velden.
Stap 2: bevestig dat het het verzoek is dat je denkt
Een verrassend aantal JSON-fouten zijn in werkelijkheid routeringsfouten. Als de submit-handler een uitzondering gooit voordat hij preventDefault aanroept, doet de browser een native formulier-POST naar het adres in het action-attribuut, en de server antwoordt met een volledige HTML-pagina. De handler draait nooit, dus er is nooit om JSON gevraagd.
Vergelijk de URL in het paneel met de URL die je JavaScript aanroept. Zoek naar een omleiding die ergens anders eindigt, een ontbrekende of extra schuine streep aan het eind, een methodeverschil waarbij de handler GET stuurt en het eindpunt POST accepteert, of een formulier dat naar een relatief pad post vanaf een pagina die is verhuisd. Volg de omleidingsketen tot het einde en noteer de uiteindelijke URL, niet de eerste.
Controleer daarna de initiator. Als het verzoek uit een documentnavigatie kwam en niet uit je script, heb je het probleem al gevonden, en geen enkele wijziging aan een responsparser helpt.
Stap 3: deel het resultaat in vier categorieën in
Elk resultaat hoort precies bij één van deze, en de categorie bepaalt waar je verder kijkt.
| What you see | Category | Where to look |
|---|---|---|
| Request never finishes, TLS or connection error | No usable response | Endpoint reachability, certificate, front-end timeout |
| Request completes, script gets an opaque failure | Access blocked | Cross-origin policy, credentials mode, missing response header |
| 200 with an HTML body or a login form | Wrong representation | Redirect, expired session, route fallback, error handler |
| Body begins with a notice then valid JSON | Wrong representation | Server-side warning output before serialisation |
| Valid JSON, framework rejects it | Contract mismatch | Envelope shape, field names, value types |
De middelste rijen zijn degene die mensen verkeerd diagnosticeren, omdat de status 200 is. Een inlogpagina die je na een omleiding bereikt, is een volstrekt goede HTTP-respons en een zeer slecht JSON-document. Het contenttype application/json lost dat niet op: het is een etiket, en een verkeerd gelabelde body laat zich nog steeds niet parsen. Toegang geblokkeerd is de rij die op een afgerond verzoek lijkt: het paneel toont een status, maar het cross-originbeleid houdt de body tegen, dus voor het aanroepende script is die even onbruikbaar als een time-out.
De vierde rij is even gewoon op oudere PHP- en frameworkstacks. Een deprecation-waarschuwing die vóór de gecodeerde payload wordt afgedrukt, levert een body op die geen enkele JSON-parser accepteert, en vaak is die waarschuwing de hele verklaring.
Stap 4: lees de parserfout letterlijk
De fouttekst noemt het eerste teken dat het parsen brak, en dat identificeert de categorie meestal zonder enige gok.
| Error text (Chrome/V8) | What it means |
|---|---|
| Unexpected token < in JSON at position 0 | The body starts with markup: an HTML page or an error document |
| Unexpected end of JSON input | Empty body, or the response was truncated |
| Unexpected token o in JSON at position 1 | You parsed something already parsed, typically an object rather than text |
| Unexpected token u | The body starts with the bare word undefined, usually an empty response rendered as a string |
Een lege body met een 200 is een apart geval: het eindpunt liep, produceerde niets, en iets verderop behandelde dat als succes. Dat is een contractfout van de applicatie en geen transportfout, en die hoort in de volgende stap, niet in de proxy. Firefox en Safari formuleren dezelfde vier fouten anders, dus vergelijk op het teken of op de lege body en niet op de exacte string.
Stap 5: repareer de laag waar het bewijs naar wijst
Bij een routeringsprobleem repareer je het verzoek: corrigeer de action, voorkom dat de handler erdoorheen valt, laat eindpunt en methode kloppen en test opnieuw. Bij een weergaveprobleem repareer je wat de body produceerde, en zoek je uit waarom een foutdocument wordt teruggegeven waar gegevens werden verwacht. Bij een contractprobleem lees je de werkelijk verwachte structuur van het framework voor de versie die je draait, en vorm je de respons daarnaar in plaats van de client losser te maken.
Een respons met geblokkeerde toegang vraagt een eigen ronde, omdat het verzoek zelf correct kan zijn en de body je code misschien nooit bereikt. Vergelijk de Access-Control-Allow-Origin van de respons met de exacte Origin die de browser stuurde, en bedenk dat een jokerteken geen verzoek met inloggegevens vervult. Koppel credentials: 'include' aan de client aan Access-Control-Allow-Credentials: true op de server, en controleer of de aanroep de modus no-cors gebruikte, waarin een ondoorzichtige body het gedocumenteerde resultaat is en geen defect.
De parser vervangen door iets toegeeflijks is de enige reparatie die je moet vermijden. De respons als code evalueren, of tekst afpellen tot het parseert, verbergt de contractafwijking en voegt een injectieoppervlak toe in ruil voor stilte. Evenzo moet de status apart van de body worden gecontroleerd: een 200 met een foutobject op applicatieniveau is normaal in veel API’s en moet worden afgehandeld door de payload te lezen.
Als je hulp van iemand anders nodig hebt, deel dan een minimale registratie — methode, URL, status, uiteindelijke URL na omleidingen, contenttype van de respons, vorm van de body en het verwachte contract — in plaats van een ruwe export. Netwerkarchieven bevatten cookies, tokens, querystrings en de persoonsgegevens die in het formulier stonden, en een automatische opschoner is geen beoordeling.
- Leg het verzoek vast zonder filter voordat je code wijzigt.
- Bevestig dat de initiator je script was en dat de uiteindelijke URL je eindpunt is.
- Classificeer de respons voordat je de parser aanraakt.
- Repareer routering, toegang, weergave of contract, en nooit door de parser losser te maken.