How to turn real exports into demo-safe fixtures
- Step 1Capture a real API response or export — Grab the JSON your frontend actually consumes (a saved network response, an API dump) or a CSV export. Realistic structure is the point — it's why your fixture renders like production. The tool reads CSV (comma-delimited, first row = header) and JSON (object, array, nested); it does not read
.xlsx/.ods. - Step 2Drop the file onto the tool — PapaParse (CSV) or
JSON.parse(JSON) runs in your browser tab; nothing is uploaded. The JSON path is taken when the filename ends in `.json` — for an API response saved without an extension, rename it to.jsonor setformat: jsonso it isn't parsed as a one-column CSV. - Step 3Make sure your PII keys use recognised names — Replacement is keyed on the whole key/column name. A key literally named
name,email,phone,address,city,zip,ssnis faked. API responses often usecustomerName,emailAddress, orphoneNumber— these are NOT exact tokens and won't be matched, so rename them at the leaf level (or map them) before scrambling if you want them faked. - Step 4Set a seed so fixtures stay stable — For committed fixtures and visual snapshots, set a numeric
seed. The tool callsfaker.seed(n)so the same source + same seed produces byte-identical fakes every rebuild — no spurious diffs in your Storybook stories or snapshot tests. Leave it blank only for throwaway one-off demos. - Step 5Scramble and check the structure round-tripped — Every PII-named key/column is overwritten with a faker value; the count is reported. Open the JSON output and confirm nesting, arrays, and non-PII fields are intact — they should be pretty-printed and structurally identical apart from the faked leaves.
- Step 6Drop the fixture into your project — The result downloads as
<name>-scrambled.<ext>. Commit it to your fixtures folder, point your mock server or Storybook story at it, or use it to seed local dev. Keep the real export out of the repo — the scramble is one-way with no reverse mapping.
What's faked vs. what keeps your UI rendering the same
How identifier fields and structural/feature fields are treated. Detection is name-based against a fixed regex (lib/security/security-processor.ts); JSON is walked recursively.
| Field | Example key | After scrambling | Effect on the demo |
|---|---|---|---|
| Display identifier | name, email | Faked | List/detail views show a believable fake person |
| Contact field | phone, address, city | Faked | Contact cards render with fake but well-formed values |
| Record key | id, uuid, accountId | Preserved | Routing, selection and joins behave as in production |
| State / status | status, active, plan | Preserved | The same UI states (badges, banners) render |
| Numeric / money | amount, count, mrr | Preserved | Charts, totals and formatting match production |
| camelCase PII key | customerName, emailAddress | Preserved (NOT matched) | Anchored regex expects bare tokens -> rename leaf keys to fake them |
The complete control surface
Every control this tool exposes, from lib/security/security-tool-schemas.ts. No per-field, masking, or fake-format option exists.
| Control | Type / values | Default | What it actually does |
|---|---|---|---|
seed | number (optional) | (blank) | Blank = fresh randomness. A number calls faker.seed(n) so fixtures are byte-stable across rebuilds. Determinism, not encryption, not reversible |
format | enum: auto / csv / json | auto | Server-safe auto treats a leading [/{ as JSON, else CSV. In-browser the JSON path is the .json extension. Force with csv / json |
| Field / column list | (not a control) | — | Fixed in code (PII_FIELDS_REGEX). Cannot be edited in the UI |
Tier, formats, and size limits
Metadata from lib/security/security-tools-registry.ts and limits from lib/tier-limits.ts. Fixtures are usually small, so limits rarely apply.
| Property | Value | Note |
|---|---|---|
| Minimum tier | Pro | minTier: "pro" |
| Input formats | CSV, JSON | JSON via JSON.parse; no .xlsx/.ods |
| Output | Pretty-printed JSON (or CSV) | JSON re-serialised with 2-space indent via JSON.stringify(.., null, 2) |
| Multiple files | Accepted | acceptsMultiple: true — scramble several fixtures at once |
| Pro limits | 100 MB / 5 files | Security family |
| Pro-media / Developer | 500 MB / 50 · 2 GB / unlimited | Higher tiers |
Cookbook
Before/after fixtures for demos and Storybook. Identifier fields get faked; structure, IDs and numbers survive so the UI renders identically. Set a seed for fixtures you commit.
API list response as a Storybook fixture
An array of records for a customer table story. name and email are faked; id, status, and mrr survive so the table's badges, sorting, and totals render exactly as production.
Input (customers.json):
[
{ "id": "c1", "name": "Sarah Chen", "email": "sarah.chen@acme.io", "status": "active", "mrr": 49 },
{ "id": "c2", "name": "Tomás Reyes", "email": "treyes@globex.com", "status": "past_due", "mrr": 149 }
]
Output (customers-scrambled.json):
[
{ "id": "c1", "name": "Dr. Elena Rosales", "email": "Reanna.Lockman@yahoo.com", "status": "active", "mrr": 49 },
{ "id": "c2", "name": "Marcus Hettinger", "email": "Jaylin.Bode@gmail.com", "status": "past_due", "mrr": 149 }
]Nested detail response
A single-record detail view with a nested customer object. The walker recurses, faking name/email/phone at the leaf; order_id, total, and the items array are preserved so the detail page renders the same.
Input (order.json):
{
"order_id": "ord_8821",
"total": 129.99,
"customer": { "name": "Priya Nair", "email": "priya@shop.co", "phone": "+44 20 7946 0958" },
"items": [{ "sku": "A-12", "qty": 2 }]
}
Output (order-scrambled.json):
{
"order_id": "ord_8821",
"total": 129.99,
"customer": { "name": "Dr. Elena Rosales", "email": "Reanna.Lockman@yahoo.com", "phone": "(555) 123-4567" },
"items": [{ "sku": "A-12", "qty": 2 }]
}Seeded, stable fixture for visual snapshots
Set a seed so the committed fixture (and the snapshot it produces) is byte-identical on every rebuild — no churn in your story diffs.
Input (team.json):
[{ "id": 1, "name": "Aisha Khan", "email": "aisha@corp.net" }]
seed = 1234
Output (every build with seed 1234, identical):
[{ "id": 1, "name": "<stable fake>", "email": "<stable fake>" }]
Commit this; snapshot tests stay green across regenerations.camelCase API keys aren't matched
Many APIs use customerName / emailAddress / phoneNumber. These are NOT exact PII tokens, so they pass through with real data. Rename the leaf keys to the bare token before scrambling if you need them faked.
Input (resp.json):
{ "customerName": "Li Wei", "emailAddress": "li.wei@x.com", "plan": "pro" }
Output (resp-scrambled.json): <-- real PII survives
{ "customerName": "Li Wei", "emailAddress": "li.wei@x.com", "plan": "pro" }
Fix: rename leaf keys to name / email, then re-run:
{ "name": "Mavis Goldner", "email": "Lonnie_Cremin@hotmail.com", "plan": "pro" }CSV fixture for a seed script
Seeding local dev from a CSV export. PII columns are faked; the IDs and statuses your seed script and foreign keys rely on are preserved.
Input (seed.csv): id,name,email,role,active 1,Sarah Chen,sarah.chen@acme.io,admin,true Output (seed-scrambled.csv): id,name,email,role,active 1,Dr. Elena Rosales,Reanna.Lockman@yahoo.com,admin,true id / role / active preserved -> your seed script and FKs still work.
Edge cases and what actually happens
camelCase PII key like `customerName` not matched
Not matchedAPI responses commonly use camelCase keys (customerName, emailAddress, phoneNumber). The PII regex is anchored to the whole key name and expects bare tokens, so these are NOT matched and real PII survives into your fixture. Rename the leaf keys to name, email, phone before scrambling, or transform the response shape first.
PII key holds a nested object
PreservedIn the JSON walk a matched key is replaced only when its value is a string or number. If address holds an object ({"line1":..., "city":...}), the parent isn't overwritten — the walker recurses, so a nested city leaf gets faked but line1 (not a token) survives. Structured address objects need PII tokens at the leaf level to be fully faked.
PII inside a free-text field (bio, description)
By designThe tool fakes whole values at matched keys and never scans inside strings. A real name or email written inside a bio, description, or message field is shown in your demo unchanged because the key isn't a PII token. Scrub those strings first with email-phone-scrubber, which emits fixed [REDACTED_*] tags.
JSON response saved without a .json extension
Mis-parseIn-browser the JSON path runs only when the filename ends in .json. A saved API response named response or data.txt is handed to PapaParse as a one-column CSV and produces a useless fixture. Rename to .json, rely on the server-safe auto sniff of a leading [/{, or set format: json.
Fixture churns on every rebuild
Use a seedWithout a seed, every scramble produces different fakes, so a committed fixture (and its visual snapshot) changes on every regeneration and pollutes diffs. Set a numeric seed so the same source yields byte-identical output every time — stable fixtures are what snapshot tests need.
Type changes after faking (number-like email column)
Heads-upFaker replacements are type-appropriate to the PII kind, not to your original value. A phone stored as a number in source JSON becomes a faker phone STRING like (555) 123-4567. If your TypeScript types or formatters expect a numeric phone, adjust the type or post-process — the fake matches the PII kind, not the source primitive.
SSN field becomes 9 plain digits
Expectedssn / tax_id keys are filled with faker.string.numeric(9) — nine random digits, no dashes. If a demo component formats or validates an SSN as NNN-NN-NNNN, the fake won't match that mask. That's the actual behaviour; there's no SSN-format option.
Malformed JSON response
ErrorJSON goes through JSON.parse; a trailing comma, single quotes, or a truncated save throws and produces nothing. Re-capture a clean response or repair it before scrambling. CSV is more forgiving — PapaParse parses ragged rows rather than throwing.
Same fake person not consistent across the fixture
Not preservedEach matched value gets an independent fake, so the same real customer appearing in two parts of a nested response (or across two fixture files) becomes different fakes. If your demo relies on the same name showing in a list and a detail view, you may need to hand-edit the fixture so the linked records agree.
Frequently asked questions
Will my demo render the same as on real data?
Yes for everything that isn't a PII identifier. Non-PII fields — IDs, statuses, amounts, dates, flags — and the JSON structure are preserved, so your components hit the same code paths and the same UI states. Only display identifiers (names, emails, phones, addresses) change to fakes.
Does it preserve nested JSON structure?
Yes. JSON is walked recursively, so nested objects and arrays keep their shape; only keys matching a PII token with a string/number value are replaced, and the result is re-serialised pretty-printed with 2-space indent. Your fixture stays valid against the same types.
My API uses camelCase keys like `customerName` — are they faked?
No. The regex expects bare tokens (name, email, phone, ...), so camelCase or compound keys like customerName, emailAddress, phoneNumber are not matched and real PII survives. Rename the leaf keys to the bare token before scrambling, or transform the response shape first.
How do I stop my committed fixtures from churning?
Set a numeric seed. The tool calls faker.seed(n), so the same source plus the same seed produces byte-identical fakes every rebuild — no spurious diffs in committed Storybook stories or visual-snapshot tests. Leave the seed blank only for throwaway demos.
What if a PII key holds a nested object?
The parent isn't replaced — replacement only happens when the matched key's value is a string or number. The walker recurses into the object, so nested PII-token leaves (like a city inside an address object) get faked individually. Structured objects need PII tokens at the leaf level to be caught.
Does a phone stored as a number stay a number?
No. Faker replacements are type-appropriate to the PII kind, not your source primitive, so a numeric phone becomes a faker phone string like (555) 123-4567. If your types or formatters expect a number, adjust the type or post-process the fixture.
Can it scrub PII inside a bio or description field?
No — it fakes whole values at matched keys and never scans inside strings. For a name or email written inside a bio, description, or message field, run that text through email-phone-scrubber first; it emits fixed [REDACTED_*] tags so your demo never shows the real value.
Does my real export get uploaded?
No. The live tool runs in your browser — PapaParse and faker are loaded client-side and the file is parsed and rewritten in the tab. The real data never leaves your machine; only the scrambled fixture is downloaded for your project.
Will the same person look consistent across the fixture?
No. Each matched value gets an independent fake, so one real customer appearing in a list and a detail view becomes two different fakes. If your demo depends on them matching, hand-edit the linked records in the fixture after scrambling.
Can I customise which keys get faked?
No. The PII field set is fixed in code with no UI control. To fake a non-matching key, rename it to a recognised token (name, email, phone, address, city, zip, ssn, ...) at the leaf level before running.
What plan and sizes do I need?
This is a Pro-tier security tool, not on Free. Limits are Pro 100 MB / 5 files, Pro-media 500 MB / 50, Developer 2 GB / unlimited — far larger than any fixture, so size rarely matters here.
What else helps when building demo data?
Scrub free-text fields first with email-phone-scrubber. If you keep a real export around for reference, encrypt it with aes-256-encryptor (Web Crypto AES-GCM 256, PBKDF2) so it isn't sitting in plaintext. Hash committed fixtures with multi-hash-fingerprinter to detect unintended changes between builds.
Privacy first
Every JAD Security operation runs entirely in your browser. Files, passwords, and PGP private keys never leave your device — verified by zero outbound network requests during processing.