- Je onderscheidt geldige JSON, een geldig schema en een inhoudelijk juist antwoord.
- Je weigert ambigue gegevens en stopt na een begrensd aantal pogingen.
Een model kan keurig ogende JSON geven met een verkeerd label of een verzonnen citaat. We bouwen daarom drie poorten: parsen leest de tekst, het domeincontract toetst velden en bewijs, en een inhoudelijke beoordeling toetst de betekenis. Geen van die stappen vervangt automatisch de volgende.
Deze oefening verwerkt eigen fictieve tickets met echte Python-controles. Ze doet geen modelcall. Een opgegeven JSON-schema kan de mogelijke vorm beperken, maar bewijst op zichzelf niet dat een antwoord waar is.
1. Lees één begrensd JSON-object
De standaarddecoder leest JSON met json.loads. Onze applicatie vereist bovenaan een object, maximaal 4096 UTF-8-bytes en maximaal acht geneste containers. Dit zijn oefenafspraken; JSON zelf staat ook andere hoofdwaarden toe.
import json
print(json.loads('{"id":"r1"}'))
# {'id': 'r1'}
print(len('é'),len('é'.encode('utf-8')))
# 1 2
Controleer vóór het decoderen de bytegrootte met encode. De tweede regel laat zien waarom tekens tellen onvoldoende is. Accepteer geen codehekken of uitleg rondom het object en gebruik nooit eval voor modeltekst.
Een dictionary kan dubbele sleutels al hebben overschreven. Gebruik daarom object_pairs_hook: de decoder geeft ieder object eerst als een lijst sleutel/waardeparen aan jouw functie. Die kan een herhaalde sleutel weigeren vóór informatie verdwijnt.
def unique(pairs):
result = {}
for key,value in pairs:
if key in result:
raise ValueError('dubbel')
result[key] = value
return result
raw = '{"id":"r1","id":"r2"}'
try:
json.loads(raw,
object_pairs_hook=unique)
except ValueError:
print('geweigerd') # geweigerd
Ook getallen vragen een expliciete afspraak. De Python-decoder accepteert standaard NaN en Infinity als uitbreiding; parse_constant kan ze weigeren. Een gewone exponent zoals 1e999 kan daarnaast in een oneindige float eindigen, dus controleer gedecodeerde floats met math.isfinite.
def no_constant(text):
raise ValueError('geen JSON-getal')
try:
json.loads('{"x":NaN}',
parse_constant=no_constant)
except ValueError:
print('geweigerd') # geweigerd
Loop na het decoderen door dictionaries en lijsten. De hoofdcontainer heeft diepte één; een container daarin diepte twee. Gewone getallen en strings verhogen het aantal geneste containers niet.
def depth(value):
if isinstance(value,dict):
children = value.values()
elif isinstance(value,list):
children = value
else:
return 0
return 1 + max(
(depth(x) for x in children),
default=0)
print(depth({'x':[{'y':1}]})) # 3
Controleer tijdens diezelfde wandeling ook strings met encode('utf-8'), en controleer sleutels in de objecthook. Zo weiger je losse Unicode-surrogaten, ook wanneer ze via JSON-escapes binnenkomen. Vertaal decodeer-, Unicode- en recursiefouten naar ValueError; maak parse_contract_json met alle genoemde grenzen.
012. Toets velden en letterlijk bewijs
Ons ticket heeft exact id, label en evidence. Het ID hoort bij de aanvraag; label is factuur, storing, vraag of onduidelijk. Een inhoudelijk label heeft evidence met exact start, end en text; bij onduidelijk moet evidence juist None zijn.
Neem “Mijn factuur mist.”. De eerste vijf tekens zijn “Mijn ”; factuur begint op index vijf en eindigt exclusief op twaalf. Het bewijs kan dus onafhankelijk worden teruggevonden.
source = 'Mijn factuur mist.'
evidence = {'start':5,'end':12,
'text':'factuur'}
ticket = {'id':'r1','label':'factuur',
'evidence':evidence}
raw = json.dumps(ticket)
value = parse_contract_json(raw)
checked = validate_ticket(
value,'r1',source)
print(source[5:12]) # factuur
print(checked['label']) # factuur
Maak validate_ticket met gehele Python-posities: booleans zijn hier geen geldige indices. Het eindpunt moet groter zijn dan het begin en binnen source liggen; de tekst moet exact met die uitsnede overeenkomen. Geef een nieuwe dictionary terug, inclusief een kopie van evidence.
Hetzelfde citaat met label storing zou deze vormcontrole nog steeds passeren. Het label is immers toegestaan en het citaat bestaat echt; of het citaat het label ondersteunt is een inhoudelijk oordeel. Gebruik Python-tekenindices consequent: een JavaScript-client heeft voor Unicode een expliciete omzetting nodig.
023. Geef herstel een eindpunt
Een ongeldig ticket krijgt hoogstens een beperkt aantal herstelpogingen. De applicatie houdt de teller bij; het model kan zijn eigen limiet niet verhogen. Onze helper kiest alleen accept, retry of reject en doet zelf geen nieuwe aanvraag.
for attempt in [1,2]:
result = accept_ticket(
'{}','r1',source,attempt)
print(result['status'])
# retry
# reject
print(accept_ticket(
raw,'r1',source,1)['status'])
# accept
Maak accept_ticket met standaard twee pogingen en een instelbare limiet van één tot drie. Controleer ongeldige tellerargumenten vóór de afhandeling. Een fout uit de parser of ticketvalidator wordt retry zolang een poging resteert, anders reject; in beide gevallen is value None.
Verander ontbrekend of verkeerd bewijs niet stilzwijgend in een plausibel citaat. Een afwijzing is informatie over wat de applicatie nog niet betrouwbaar kan gebruiken. Gerichte herstelvragen en inhoudelijke evaluatie blijven afzonderlijke ontwerpkeuzes.
03Wat neem je mee?
- JSON-syntax, een domeincontract en betekenis zijn drie verschillende poorten.
- Bytegrenzen en decoderhooks maken het parserbeleid controleerbaar.
- Beperkt herstel voorkomt een eindeloze lus zonder geldige uitkomst.
Hierna gebruik je hetzelfde principe voor voorstellen om tools uit te voeren.