JSON-inname: weiger dubbelzinnige bytes vóór je het datamodel toetst
De kern
Een decoder die iets teruggeeft, bewijst nog geen eenduidige JSON-overdracht. Scheid bytebudget, representatie, decodering en domeincontrole.
Voorkom verlies voordat je velden ziet
Stel dat een antwoord dezelfde sleutel tweemaal bevat. Als de decoder er stilzwijgend één bewaart, kan de latere schemacontrole niet meer zien dat er een conflict was. Een adapter heeft daarom een innamegrens nodig vóór de controle op verplichte velden. Die grens bepaalt welke aangeleverde bytes je überhaupt als een bruikbaar document beschouwt.
RFC 8259 beschrijft UTF-8 voor interoperabele uitwisseling, beveelt unieke objectnamen aan en laat NaN niet toe in de getalgrammatica.[2] De onderstaande lokale afspraak weigert dubbele namen in plaats van een winnaar te kiezen. Dat is bewust strenger dan een vrijblijvende waarschuwing; noem het een adapterbeleid, geen universeel gedrag van iedere JSON-parser.
Zet de controles in een vaste volgorde
- Controleer het type en het budget van de reeds ontvangen bytes.
- Laat alleen de afgesproken mediarepresentatie door.
- Decodeer als UTF-8 zonder vervangende tekens te verzinnen.
- Weiger dubbele namen en niet-JSON-constanten bij het parsen.
- Pas daarna het resourceschema toe; die laatste stap is hier niet gebouwd.
De uitgevoerde bytegevallen
De geldige letterlijke invoer is {"n":1.00,"enabled":true}. Decimal bewaart in deze proef de tekstuele decimaalvorm 1.00; enabled blijft een boolean. De limiet van 4096 bytes en de keuze voor application/json zijn lokale oefenafspraken. Andere mediatypen worden niet automatisch geaccepteerd omdat hun naam op json eindigt.
Gebruik Kopieer code om alle code exact te kopiëren. Handmatig selecteren kan de laatste regelovergang weglaten.
import json
from decimal import Decimal
def unique(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError("duplicate member")
result[key] = value
return result
def no_constant(value):
raise ValueError("non-JSON constant")
def intake(raw, media_type):
if type(raw) is not bytes or len(raw) not in range(4097):
raise ValueError("byte budget or type")
if media_type.split(";", 1)[0].strip().lower() != "application/json":
raise ValueError("unsupported representation")
return json.loads(raw.decode("utf-8"), object_pairs_hook=unique,
parse_float=Decimal, parse_constant=no_constant)
# Synthetic wire bytes, not captured provider traffic.
good = intake(b'{"n":1.00,"enabled":true}', "application/json")
assert type(good["n"]) is Decimal and str(good["n"]) == "1.00"
assert type(good["enabled"]) is bool and good["enabled"] is True
bad = [b'{"a":1,"a":2}', b'{"nested":{"x":1,"x":2}}',
b'{"n":NaN}', b'{"x":"\xff"}', b'{} {}', b' ' * 4097]
for raw in bad:
try:
intake(raw, "application/json")
except (ValueError, UnicodeError):
pass
else:
raise AssertionError("bad wire bytes accepted")
try:
intake(b'{}', "text/html")
except ValueError:
pass
else:
raise AssertionError("HTML representation accepted")
print(json.dumps({"decimal_text": str(good["n"]), "boolean_type": type(good["enabled"]).__name__,
"wire_rejections": len(bad), "media_rejections": 1}, sort_keys=True))
{"boolean_type": "bool", "decimal_text": "1.00", "media_rejections": 1, "wire_rejections": 6}
De uitvoer telt zes geweigerde bytefixtures: een dubbele naam op het hoogste niveau, een geneste dubbele naam, NaN, een ongeldige UTF-8-byte, twee opeenvolgende documenten en overschrijding van het budget. Een afzonderlijke representatieproef weigert text/html. Dit zijn echt uitgevoerde lokale gevallen, geen onderschepte providerantwoorden of metingen van een API.
Geef de volgende laag geen schijnzekerheid
Een geaccepteerde boolean kan nog steeds verkeerd zijn voor een veld dat een aantal verwacht. Ook een syntactisch correct object met een ontbrekende sleutel blijft mogelijk onbruikbaar. De oefening doet geen vergelijking met een verwachte payload en gebruikt geen Python-dictionarygelijkheid om types te bewijzen. De twee expliciete typeasserts gelden alleen voor de gekozen geldige fixture.
De bytes zijn al volledig in geheugen: dit is geen geteste downloadlimiet, decompressiebeveiliging of streamingparser. Recursiediepte, extreem lange getallen, Unicode-normalisatie en eindige Decimal-berekeningen vragen aanvullende grenzen. Bewaar bij een fout liever een veilige foutcategorie en contractversie dan de ruwe antwoordinhoud. Brononderzoek en offline uitvoering zijn gereed; netwerkgedrag, rechten en onafhankelijke taal- en feitenreview blijven open.
Bronnen & onderbouwing
- Oorspronkelijke brontitel: RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format
Geraadpleegd: 2026-10-18T22:58:30.094971+00:00