Interactive demo · synthetic dataEvery record on this page is fabricated.
Extraction Without a Schema
Records recognised by shape, not by path, from a GraphQL API that is reshaped without notice.
The production code behind this demo is my own work — design, implementation.
Apply one, two or all three. The response changes underneath the extractor; the records it returns do not.
THE RESPONSE AS RECEIVED · 2,011 CHARS
{
"data": {
"jobAd": {
"__typename": "JobAd",
"id": "job-4410",
"title": "Warehouse Supervisor",
"reference": "SN-WH-4410",
"location": {
"label": "Bandung, West Java"
},
"postedAt": "2026-02-02",
"expiryDate": "2026-03-04",
"applicationCount": 3,
"applications": {
"edges": [
{
"node": {
"__typename": "Candidate",
"id": "cand-8871",
"fullName": "Marisol Adeyemi-Okonkwo",
"email": "m.adeyemi@example.invalid",
"phone": "+62 811 555 04",
"appliedDateTime": "2026-02-11T09:12:00+07:00",
"statusInfo": {
"label": "Shortlisted"
},
"attachments": [
{
"__typename": "CandidateAttachment",
"url": "https://files.example.invalid/a/9f2c.pdf",
"fileName": "Marisol_Adeyemi_CV.pdf",
"type": "RESUME"
},
{
"__typename": "CandidateAttachment",
"url": "https://files.example.invalid/a/9f2d.docx",
"fileName": "cover letter.docx",
"type": "COVER_LETTER"
}
]
}
},
{
"node": {
"__typename": "Candidate",
"id": "cand-8872",
"fullName": "Ignatius Broome",
"email": "i.broome@example.invalid",
"phone": "+62 811 555 05",
"appliedDateTime": "2026-02-12T14:40:00+07:00",
"applicationStatus": "Interviewing",
"attachments": [
{
"__typename": "CandidateAttachment",
"url": "https://files.example.invalid/a/1a04.pdf",
"fileName": "broome-cv-2026.pdf"
}
]
}
}
]
}
}
}
}EXTRACT.TS · WALKS THE SHAPE
1
2
3
Recognised by shape, not by path: a name, an id, and a signal that this is a person who applied for something.
NAIVEEXTRACT · HARD-CODES THE PATH
The path resolved. This is the version almost everyone writes first, and it is correct until the morning the vendor renames a key.
- Marisol Adeyemi-Okonkwo
- Ignatius Broome
WHAT CAME BACK
Warehouse Supervisor
job-4410 · SN-WH-4410 · Bandung, West Java · CLOSES 2026-03-04 · 3 APPLICATIONS · JobAd
| NAME | ID | CONTACT | APPLIED | STATUS | JOB |
|---|---|---|---|---|---|
| Marisol Adeyemi-Okonkwo | cand-8871 | m.adeyemi@example.invalid+62 811 555 04 | 2026-02-11 | Shortlisted | job-4410 |
| Ignatius Broome | cand-8872 | i.broome@example.invalid+62 811 555 05 | 2026-02-12 | Interviewing | job-4410 |
| FILE | KIND | READS AS | ATTRIBUTED TO |
|---|---|---|---|
| Marisol_Adeyemi_CV.pdf | CandidateAttachment RESUME | CV | cand-8871job-4410 |
| cover letter.docx | CandidateAttachment COVER_LETTER | COVER LETTER | cand-8871job-4410 |
| broome-cv-2026.pdf | CandidateAttachment | CV | cand-8872job-4410 |
The shape the portal returns most days. Candidates are nested inside the job and their documents are nested inside them, so every record comes out fully attributed: each CV knows which person it belongs to and which job they applied for. Nothing in the extractor knows this path exists — it is inferred from the nesting.
- The portal deletes applicant attachments 180 days after an ad closes and has no bulk download. That constraint is the first thing the README states.
- The password is typed by the human into a real browser. The script never sees or stores a credential; only the resulting session file, which is deleted after.
- This is real people's personal data. Store it access-controlled, keep it only as long as hiring needs it.
- If you need this supported rather than automated, the vendor sells an official partner application-export API. Use that instead.
What is real, and what is simulated
Runs in your browser
- extract.ts — the 200-line production extractor, ported verbatim: the recursive walk, the key-preference lists, both regexes and every guard
- looksLikeCandidate / looksLikeJob / looksLikeAttachment — the shape tests unchanged, including the rejections that throw out a company, an account, a location or a currency
- Attribution by nesting: an attachment inherits the candidate it was found inside, and that candidate inherits the job
- The merge by id that lets three partial responses fill in one record, the 40-level depth guard, and the WeakSet that stops a cycle spinning
extract.parity.test.ts rebuilds a 105,861-case matrix — 105,840 combinations of name, id, signal, contact, attachment and nesting shapes, plus 21 hand-written edge roots — and compares a SHA-256 of every result against a digest generated by running the original extractor in its own repository. It runs in npm run verify, so a widened character class or a reordered key list fails the build.
Simulated
- The browser automation around it: the sign-in the human performs, the saved session, the walk across every job ad and applicant
- Downloading a CV on sight from a short-lived signed URL, the per-job folders and the CSV index it writes
- The three mutation buttons and the hard-coded-path comparison — both written for this page, neither is production code
- Every payload, applicant, employer, file and address on this page. The hosts end in .invalid and can never resolve