GDPR-Compliant Test Data: Synthetic Generation vs. Data Masking
Every team building against real customer data eventually has to answer the same question: how do you get realistic-looking test data into dev and CI without shipping actual customers' names, emails, and addresses along with it? The usual first answer is to mask a copy of production. The safer answer is to stop starting from production at all. This post looks at why schema-driven synthetic generation is a stronger starting point for GDPR-compliant test data than masking, and where it isn't a substitute for the real thing.
One note up front: nothing here is legal advice, and no generation technique by itself "guarantees" compliance — GDPR obligations depend on your specific processing activities, and you should talk to counsel about your own setup. What follows is a technical comparison of two approaches and the risk each one carries.
Synthetic data vs. data masking, in short
Data masking (also called anonymization or de-identification) starts with a real dataset and transforms it — hashing emails, truncating names, shuffling values between rows — to obscure the original values while keeping the dataset's shape. Synthetic data generation never starts with real records at all: it produces new values from a schema or template, so there's no source personal data to protect in the first place. Masking reduces risk in an existing dataset; synthetic generation removes the dataset that carried the risk.
What data masking actually does, and where it stops
Masking techniques fall into a few common buckets:
- Hashing or tokenizing identifiers (emails, SSNs, account numbers) so the same input always maps to the same output, preserving joinability.
- Truncating or generalizing fields (a birthdate becomes a birth year, a full address becomes a postal code).
- Shuffling values within a column across rows, so
nameandemailstill look real but no longer belong to the same person.
All of these keep the statistical shape of production data, which is exactly why teams reach for masking: query plans, join cardinalities, and edge cases (a name with an apostrophe, a null middle field) all carry over naturally. But every one of these transformations is reversible in principle, and reversibility is the crux of the compliance problem — a hashed email is still deterministically linkable back to the same person across your dataset, and a shuffled dataset can often be re-linked using auxiliary information the attacker didn't need to be sophisticated to find.
Is anonymized data still personal data under GDPR?
Regulators have repeatedly cautioned that data isn't automatically outside GDPR's scope just because it's been labeled "anonymized." The Article 29 Working Party's opinion on anonymization techniques (WP216, later carried forward in EDPB guidance) sets a high bar: data only counts as truly anonymous if re-identification is not "reasonably likely" by any party, accounting for the cost, time, and auxiliary data an attacker could plausibly bring to bear — not just whether the direct identifiers were removed. Masked data that's merely pseudonymized (reversible with a key, or re-linkable via quasi-identifiers like ZIP code, birthdate, and gender in combination) generally still counts as personal data and stays in scope. That's a meaningful gap between what "masked" looks like to a human scanning a spreadsheet and what a regulator or a competent attacker with side information can actually do with it.
How synthetic generation sidesteps re-identification risk
Template-driven generation, which is how JsonFabrica works, closes that gap
differently: instead of transforming real rows, you write a template that
describes the shape of a document — field types, formats, relationships —
and the generator produces new values from functions like
getRandomFullName(), getRandomEmail(), or getRandomAddress(), with no
production record read or referenced at any point in the process. There is
no re-identification risk to evaluate because there's no real person's data
underlying any generated field to begin with — the risk isn't reduced, it's
structurally absent. That's the core distinction this post keeps returning
to: masking asks "how well did we obscure the real data," synthetic
generation asks "did we ever touch the real data at all."
This doesn't mean synthetic output is random noise disconnected from your domain — realistic distributions, valid formats, and correct foreign-key relationships between generated records still matter for the data to be useful, which is a separate topic covered in Random Isn't Realistic. The point here is narrower: realism and provenance are independent axes, and you can have realistic-looking data with zero personal-data provenance.
How to generate GDPR-compliant test data
A practical workflow looks like this:
- Model your schema as a template, not a data export — describe each
table or document with function placeholders (
<getRandomFullName()>,<getRandomEmail()>,<getRandomDate('2020-01-01', '2024-12-31')>) instead of copying sampled rows. - Wire up relationships explicitly. JsonFabrica's batch generation API
accepts a
relationsmap per document type (afromfield and around-robinstrategy), so a generatedorderdocument can reference a generatedcustomer'sidcorrectly instead of pointing at a random, disconnected value — the referential integrity your application and its foreign-key constraints expect, without any of the rows being real. - Fix a
seedfor reproducibility. Passing the same numericseed(anddocumentSeedfor per-document determinism) on repeated generation calls produces the same dataset every time, so a failing CI run is reproducible locally and a bug report can include the exact seed that triggered it — something a fresh masked export every night can't offer. - Regenerate instead of refreshing from production. Once the template is in place, "refreshing" your dev database means calling the generation API again, not re-exporting and re-masking a new production snapshot — which also means the masking pipeline itself (a piece of infrastructure that has to be maintained, audited, and trusted) is one less thing to keep correct.
Where synthetic data isn't a substitute for real data
Being honest about the limits matters as much as making the case above:
- Production analytics and behavioral testing. If you're validating a recommendation model or analyzing real usage patterns, synthetic data generated from a schema won't reproduce the actual distributions and correlations present in your live traffic — that's a fundamentally different problem than fixture generation.
- Migration testing against real edge cases. Data migrations often break on the specific malformed, legacy, or unusual rows that accumulated in production over years — a synthetic dataset generated fresh won't contain the exact weird rows that caused the last migration incident unless you deliberately model them into the template.
- Load shape validation at true production scale and skew. Synthetic generation can produce large volumes, but matching production's actual key-cardinality skew (a handful of accounts with a huge share of the records) takes deliberate modeling, not the default distribution.
For all three, a properly governed, access-controlled masking pipeline (or, where feasible, differential privacy techniques) is still the right tool — synthetic generation is aimed squarely at dev, CI, staging, and demo environments where the goal is realistic shape, not real production signal.
FAQ
Does using synthetic test data mean I don't need a DPA or GDPR review? No. Synthetic data generated from schemas reduces the personal-data footprint of your dev and test environments, but your overall GDPR posture still depends on everything else you process — production systems, analytics, third-party processors. Treat this as risk reduction for one specific surface, not a substitute for a compliance review.
Is pseudonymized data the same as synthetic data? No. Pseudonymized data still originates from real records (a name replaced with a token, say) and remains personal data under GDPR if it can be re-linked, even indirectly. Synthetic data has no real record as its source, so there's nothing to re-link.
Can synthetic data still leak real information if the template is bad? Yes, in principle — a template that embeds real names, emails, or IDs as literal values instead of function placeholders reintroduces the exact problem you're trying to avoid. The safety property comes from what the template generates, not from the word "synthetic" itself.
Does deterministic (seeded) generation weaken privacy? No — a seed controls which synthetic values get generated in what order, for reproducibility. It doesn't reference or derive from any real record, so fixing a seed doesn't reintroduce a link back to production data.
JsonFabrica generates test data from schemas and templates through the generation API, with seeded determinism and relational integrity built in — so dev, CI, and staging environments never need a production export to look realistic.
Generate realistic test data with JsonFabrica
Describe the shape of your data once, then generate as many fresh, realistic JSON documents as you need via a simple API call.