A surprisingly common API pattern is:
body = parseJson(request)
process(body)
as though:
valid JSON syntax
=
valid application input
It does not.
This payload is valid JSON:
{
"email": 42,
"age": -900,
"roles": "admin",
"unknownOption": true
}
but it may be completely invalid for your application.
A safer mental model is:
bytes
|
v
transport limits
|
v
JSON syntax parser
|
v
structural validation
|
v
semantic validation
|
v
authorization
|
v
trusted application model
|
v
business logic
Parsing is not validation
A JSON parser answers whether input can be interpreted as JSON. A schema answers whether it has the expected structural shape. Application code still needs to decide whether the values make sense for the current operation, user, tenant, resource state, and business rules.
1. Parsing JSON is only the first boundary
Safe JSON parsing and validation pipeline (diagram)
The parser's responsibility is usually limited to converting:
{
"quantity": 4
}
into some runtime representation such as:
object / map
quantity -> number 4
Syntax failures belong at the parser boundary
Examples:
{
"name": "Alex",
}
{
name: "Alex"
}
{
"name": "Alex"
"age": 30
}
depending on the exact input, these contain syntax that ordinary JSON parsers should reject.
Structural failures happen after parsing
{
"quantity": "four"
}
can be valid JSON syntax while violating:
quantity must be integer
Semantic failures happen later again
{
"quantity": 900
}
may satisfy:
integer
but fail:
maximum order quantity = 10
Authorization is another independent boundary
Even structurally and semantically valid JSON:
{
"projectId": "project-781",
"name": "Updated"
}
must not prove that the current caller:
owns project-781
2. Treat every external JSON value as untrusted
JSON may enter from:
- HTTP requests.
- Third-party APIs.
- Configuration files.
- Message queues.
- Webhooks.
- Browser storage.
- Database JSON columns.
- Imported files.
External input can be:
malformed
unexpected
outdated
oversized
hostile
partially compatible
Do not trust fields because TypeScript or another static type says they exist
A compile-time declaration such as:
type UserRequest = {
email: string
age: number
}
does not transform arbitrary network bytes into a validated
UserRequest.
Runtime input can still be:
{
"email": false,
"age": []
}
Validate at trust boundaries
Good places include:
HTTP controller
message consumer
file importer
webhook endpoint
configuration loader
Then pass stronger types inward
untrusted JSON
|
v
validated request DTO
|
v
domain command
|
v
business logic
This reduces repeated:
is this field really a string?
checks deep inside the application.
3. Use JSON Schema for structural contracts
JSON Schema validation layers (diagram)
JSON Schema lets you describe constraints in a machine-readable form.
Simple example
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["email", "age"],
"properties": {
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 18,
"maximum": 120
}
},
"additionalProperties": false
}
This communicates:
root must be object
email required
email must be string
age required
age must be integer
age between 18 and 120
unknown properties rejected
Type constraints matter
Do not rely on:
maximum: 120
to imply:
must be a number
Define:
"type": "integer"
explicitly when that is the contract.
Useful object keywords
type
properties
required
additionalProperties
patternProperties
propertyNames
unevaluatedProperties
Useful string constraints
minLength
maxLength
pattern
format
Useful numeric constraints
minimum
maximum
exclusiveMinimum
exclusiveMaximum
multipleOf
Useful array constraints
items
prefixItems
minItems
maxItems
uniqueItems
contains
Composition
allOf
anyOf
oneOf
not
can express more complex schemas.
Do not make schemas unnecessarily clever
If a schema requires extensive nested:
oneOf
allOf
if / then / else
references
to express business workflow, consider whether some logic belongs in normal application validation instead.
Understand format behavior
A schema such as:
{
"type": "string",
"format": "email"
}
should not lead you to assume every validator configuration will reject every string that does not resemble an email.
Verify:
validator
schema dialect
format vocabulary
configuration
and use application-level validation when stronger guarantees are needed.
4. Validate required, optional, null, and unknown fields
These four concepts are commonly confused.
Missing
{}
The property does not exist.
Null
{
"middleName": null
}
The property exists and its JSON value is null.
Empty string
{
"middleName": ""
}
The property exists and contains a string of length zero.
Optional
Usually means:
property may be absent
which does not automatically mean:
property may be null
Model nullable values explicitly
{
"type": ["string", "null"]
}
if both are valid.
Required does not define the property's type
This:
"required": ["name"]
says:
name must exist
not:
name must be a non-empty string
Unknown fields need an explicit policy
Consider:
{
"email": "user@example.com",
"isAdmin": true
}
when the contract defines only:
email
Strict option
"additionalProperties": false
can help detect:
typos
unsupported fields
unexpected client behavior
Extensible option
Some contracts intentionally ignore fields they do not understand to support forward compatibility.
Neither strategy is universally correct.
Document whether unknown properties are:
rejected
ignored
preserved
validated under extension rules
5. Define a duplicate-key policy
Consider:
{
"role": "user",
"role": "admin"
}
The member name appears twice.
Different parsers may behave differently
One may produce:
role = "admin"
another implementation may:
reject payload
and another representation may preserve more information.
This can become a security boundary problem
Suppose:
gateway validator
uses first value
application parser
uses last value
Then:
{
"isAdmin": false,
"isAdmin": true
}
can be interpreted differently by two components.
Prefer one canonical interpretation
For security-sensitive inputs, a strong defensive policy is:
reject duplicate object names
before components disagree about their meaning.
Test your actual parser
Do not assume:
the language behaves
the way another language behaves
6. Be deliberate with numbers, dates, and identifiers
JSON has numbers, not your application's numeric type system
A parser may map numbers into:
floating-point number
arbitrary precision integer
decimal
multiple numeric types
depending on the runtime.
Large integer identifiers deserve special attention
Suppose a system exchanges:
{
"orderId": 9876543210123456789
}
A runtime that represents ordinary JSON numbers using binary floating point may not preserve every sufficiently large integer exactly.
Opaque identifiers are often safer as strings
{
"orderId": "9876543210123456789"
}
especially when the application never performs arithmetic on the ID.
Money needs explicit representation
Avoid casually assuming:
19.99
will behave as an exact decimal amount in every runtime.
Depending on the system, use:
decimal representation
or
minor units:
1999 cents
with currency and rounding rules defined explicitly.
JSON has no date primitive
This:
{
"createdAt": "2026-08-29T18:00:00Z"
}
contains:
a string
until your schema and application interpret it as a timestamp.
Validate date semantics after syntax
start:
2026-09-30
end:
2026-09-01
may contain individually valid date strings but still violate:
start <= end
7. Put resource limits before expensive processing
Valid JSON can still be operationally hostile.
Huge body
{
"text": "...hundreds of megabytes..."
}
Huge array
{
"items": [
...millions of entries...
]
}
Deep nesting
{
"a": {
"b": {
"c": {
"d": {
...
}
}
}
}
}
Limit at the earliest practical boundary
Useful limits include:
HTTP body bytes
JSON nesting depth
array length
string length
object property count
batch item count
Schema limits do not replace transport limits
If the server first buffers:
2 GB request
and only afterward checks:
"maxLength": 2000
the resource problem has already occurred.
Limit downstream work too
A 100 KB payload can still request:
100,000 database operations
if one small field controls an expensive loop.
Validation should consider:
computational cost
not only payload size
Batch APIs need explicit maximums
{
"users": [...]
}
should define whether the maximum is:
100
1000
10000
rather than accepting an unbounded list.
8. Separate schema validation from business validation
Schema validation can verify:
{
"quantity": 4
}
against:
quantity:
integer
minimum 1
maximum 20
but business logic may still need:
warehouse has at least 4
customer is allowed to buy product
product is currently active
Structural validation
Is the payload shaped correctly?
Semantic validation
Do the values make sense together?
Authorization
May this caller perform
this action on this resource?
Database integrity
Can the resulting state
exist consistently?
Example
{
"userId": "user-17",
"teamId": "team-42",
"role": "admin"
}
Schema validation can verify:
all fields are strings
but cannot by itself prove:
user-17 belongs to team-42
caller may assign admin
team-42 exists
Keep authorization outside client-controlled fields
Never interpret:
{
"isAuthorized": true
}
as proof of authorization.
Authorization comes from:
authenticated identity
+
trusted policy
+
server-side resource state
9. Map JSON into trusted application types carefully
A strong boundary converts:
untrusted generic object
into:
validated typed model
Prefer explicit field mapping
CreateUserRequest
email
displayName
marketingConsent
rather than copying:
every incoming property
into an internal object.
Mass-assignment risk
Suppose your internal user model contains:
email
displayName
role
isVerified
billingPlan
while the public request should contain only:
email
displayName
A generic:
copy all JSON fields
into User
can accidentally expose sensitive properties.
Use dedicated request models
CreateUserRequest
UpdateProfileRequest
AdminUpdateUserRequest
rather than one giant model whose fields are conditionally trusted.
Do not let input select arbitrary runtime classes
Avoid unsafe patterns where JSON contains:
{
"type": "SomeRuntimeClass"
}
and the deserializer instantiates arbitrary classes based directly on untrusted names.
Map enums deliberately
{
"status": "ACTIVE"
}
should be checked against:
known supported values
and have a compatibility strategy for future values.
10. Design schemas for compatibility and versioning
JSON contracts evolve.
Adding an optional property
v1:
{
"name": "A"
}
new producer:
{
"name": "A",
"timezone": "Europe/Warsaw"
}
can break an older strict consumer if it rejects every unknown property.
Strictness has compatibility consequences
Decide whether the contract prioritizes:
strict typo detection
or
forward extensibility
and design accordingly.
Removing or renaming fields is harder
"fullName"
changing to:
"displayName"
can require:
dual-read period
explicit API version
migration
compatibility adapter
Do not silently change field meaning
A field:
"amount": 1999
should not change from:
cents
to:
whole currency units
without a contract change.
Version important schemas
Depending on architecture:
API version
event schema version
message version
configuration version
makes compatibility explicit.
Keep old event schemas when consumers need them
Event-driven systems may need to replay:
historical messages
long after the producer has changed.
11. Return useful validation errors without leaking internals
Weak API response:
{
"error": "Invalid request"
}
tells the client very little.
Structured validation error
{
"error": "validation_failed",
"issues": [
{
"path": "/email",
"code": "invalid_format",
"message": "Expected a valid email address"
},
{
"path": "/age",
"code": "minimum",
"message": "Must be at least 18"
}
]
}
Useful fields
path
machine-readable code
safe message
Do not return internal stack traces
Avoid:
{
"error": "NullPointerException",
"stack": "..."
}
Do not expose sensitive values in validation errors
For:
password
accessToken
API key
personal information
avoid reflecting complete values into:
HTTP response
logs
analytics
exception messages
Log request identity, not necessarily request content
Prefer:
request ID
route
schema version
failed path
validation code
where that provides sufficient diagnostics.
12. Avoid security-sensitive parsing shortcuts
Never execute JSON as code
Use a real JSON parser.
Do not convert JSON input into:
eval(...)
or equivalent executable syntax.
Do not trust client-controlled roles or ownership fields
{
"role": "super-admin"
}
must not grant privileges merely because it parsed successfully.
Protect object mapping
Language-specific object models can have special property behavior.
Avoid merging arbitrary untrusted keys into:
security-sensitive configuration
authorization objects
application prototypes
framework internals
Use allowlists for privileged updates
allowed:
displayName
timezone
avatar
not client-writable:
role
accountStatus
creditLimit
Validate before expensive downstream actions
Reject:
invalid IDs
oversized arrays
unsupported operations
invalid enum values
before performing:
database queries
remote API calls
file writes
Do not rely on schema validation for authorization
A structurally valid request can still be:
unauthorized
13. Debug malformed and invalid payloads systematically
Defensive JSON debugging workflow (diagram)
Step 1: preserve the exact failing payload safely
When permitted by privacy rules, reproduce with the exact structure that failed.
Redact:
tokens
passwords
personal data
secrets
Step 2: identify the failure layer
transport?
encoding?
JSON syntax?
schema?
semantic rule?
authorization?
database?
Step 3: check the root JSON type
Your endpoint may expect:
object
but receive:
[]
"hello"
42
null
all of which are JSON values.
Step 4: inspect the validation path
Prefer diagnostics such as:
/items/3/quantity
expected:
integer >= 1
actual:
0
rather than:
schema validation failed
Step 5: check duplicate fields
If behavior differs between:
API gateway
validator
application parser
test duplicate object member names explicitly.
Step 6: check schema version
An old producer may send:
v1 message
to:
v2 validator
or vice versa.
Step 7: minimize the payload
Reduce:
500-field production message
to:
smallest JSON value
that reproduces failure
Step 8: add a regression fixture
Examples:
valid minimal payload
missing required field
wrong type
null value
unknown field
duplicate key
very large array
deep nesting
large integer
invalid enum
semantic conflict
Step 9: test both acceptance and rejection
Validation tests should prove:
valid data is accepted
and
invalid data is rejected
because an overly strict validator can be just as damaging as an overly permissive one.
14. Copy/paste defensive JSON checklist
Defensive JSON checklist
Trust boundary
- Treat incoming JSON as untrusted.
- Validate HTTP requests.
- Validate webhooks.
- Validate queue messages.
- Validate imported files.
- Validate third-party API responses when assumptions matter.
- Validate configuration.
- Do not rely only on compile-time types.
Transport
- Set maximum request-body size.
- Enforce content type where appropriate.
- Define character encoding.
- Reject unsupported compression or encodings where required.
- Set request timeout.
- Avoid buffering unbounded payloads.
- Stream large supported payloads where appropriate.
Parsing
- Use a real JSON parser.
- Never eval JSON input.
- Catch syntax errors at boundary.
- Distinguish malformed JSON from schema-invalid JSON.
- Reject invalid encoding according to protocol.
- Know parser behavior for duplicate names.
- Know parser numeric representation.
- Know parser depth limits.
Root value
- Validate expected root type.
- Do not assume JSON root is always object.
- Handle array root intentionally.
- Handle null root intentionally.
- Reject scalar root when contract expects object.
Object fields
- Define expected properties.
- Define required properties.
- Define optional properties.
- Define nullable properties.
- Define unknown-property policy.
- Define extension-property policy.
- Validate property names when necessary.
Required fields
- Remember required means property must exist.
- Required does not imply non-null.
- Required does not imply non-empty string.
- Add type constraints.
- Add length constraints.
- Add semantic constraints.
Null
- Distinguish missing from null.
- Distinguish null from empty string.
- Distinguish null from zero.
- Define nullable fields explicitly.
- Avoid silently coercing null to defaults unless contract specifies it.
Unknown properties
- Decide reject vs ignore vs preserve.
- Use strict mode when typo detection is valuable.
- Consider forward compatibility.
- Do not accidentally copy unknown fields into domain models.
- Log unexpected properties only if privacy-safe.
- Test client compatibility.
additionalProperties
- Understand default behavior.
- Use false for strict object contracts when appropriate.
- Use schema value to validate extension fields where useful.
- Review interactions with composed schemas.
- Test nested objects separately.
unevaluatedProperties
- Use when composition requires control over properties not already evaluated.
- Verify validator supports selected draft.
- Test with allOf / anyOf / oneOf combinations.
- Avoid assuming it behaves identically to additionalProperties in every schema structure.
Duplicate keys
- Prefer unique object member names.
- Define duplicate-key policy.
- Consider rejecting duplicates.
- Test gateway parser.
- Test application parser.
- Test schema validator.
- Ensure security layers interpret payload consistently.
- Do not rely on first-value vs last-value behavior.
JSON Schema
- Declare intended schema dialect.
- Use type explicitly.
- Use required explicitly.
- Add properties.
- Add array item schemas.
- Add length constraints.
- Add numeric constraints.
- Add enum / const where appropriate.
- Add unknown-field policy.
- Keep schema readable.
Draft version
- Know which JSON Schema draft is used.
- Use Draft 2020-12 keywords correctly when using that dialect.
- Do not copy old tuple syntax blindly.
- Verify validator supports required vocabulary.
- Keep schemas and validator versions compatible.
Strings
- Set minLength where empty is invalid.
- Set maxLength.
- Use pattern only when appropriate.
- Avoid overly complex regular expressions.
- Normalize text only according to application policy.
- Validate Unicode input.
- Test large strings.
- Test unusual whitespace.
format
- Know whether format is annotation or assertion in your setup.
- Verify validator configuration.
- Do not rely on format blindly.
- Perform application-level semantic checks where required.
- Test email behavior.
- Test URI behavior.
- Test date-time behavior.
- Keep business validation separate.
Numbers
- Know parser number representation.
- Define integer vs number.
- Set minimum.
- Set maximum.
- Consider multipleOf.
- Test very large values.
- Test negative values.
- Test zero.
- Test decimal precision.
- Do not use floating point casually for exact money.
Large integers
- Test values above common floating-point exact-integer range.
- Use string for opaque numeric-looking IDs where appropriate.
- Use arbitrary precision when contract requires it.
- Keep producer and consumer behavior compatible.
- Test round trips.
Money
- Define currency.
- Define precision.
- Define rounding.
- Consider decimal type.
- Consider integer minor units.
- Avoid binary floating-point assumptions.
- Validate ranges.
- Validate sign.
Dates
- Remember JSON has no native date type.
- Represent dates as documented strings or numbers.
- Define timezone behavior.
- Prefer explicit timestamp conventions.
- Validate format.
- Parse using date library.
- Validate semantic relationships.
- Test invalid calendar dates.
Identifiers
- Treat IDs as identifiers, not arithmetic values.
- Define UUID or other format if required.
- Validate allowed length.
- Avoid accepting arbitrary paths as IDs.
- Verify referenced resource exists separately.
- Authorize access separately.
Booleans
- Require boolean when contract requires boolean.
- Do not silently coerce "true" to true unless contract explicitly allows it.
- Do not treat 1 and 0 as booleans accidentally.
- Test null and missing independently.
Arrays
- Define items.
- Define minItems.
- Define maxItems.
- Use uniqueItems only when semantics require it.
- Validate each element.
- Limit batch size.
- Avoid unlimited fan-out downstream.
- Test empty arrays.
- Test duplicate items.
Tuples
- Use prefixItems in Draft 2020-12 for positional schemas.
- Use items for remaining array items according to selected dialect.
- Prefer objects when named fields communicate meaning better than positions.
Nested objects
- Define nested schemas.
- Set depth limits outside schema where necessary.
- Avoid deeply recursive untrusted structures.
- Test missing nested object.
- Test null nested object.
- Test wrong nested type.
- Test unknown nested fields.
Composition
- Use allOf deliberately.
- Use anyOf deliberately.
- Use oneOf only when branches are truly distinct.
- Test ambiguous oneOf cases.
- Keep discriminator logic explicit where needed.
- Avoid schema complexity that obscures business rules.
Conditional schemas
- Use if / then / else when structural conditional validation is clear.
- Keep complex workflows in application logic.
- Test every branch.
- Avoid duplicating business rules inconsistently across schema and code.
References
- Use $ref for reusable schemas.
- Manage schema IDs carefully.
- Avoid uncontrolled remote schema retrieval at runtime.
- Bundle or pin schemas where reliability requires it.
- Version referenced schemas.
- Test missing reference behavior.
Resource limits
- Limit body bytes.
- Limit nesting depth.
- Limit object property count.
- Limit array length.
- Limit string length.
- Limit batch size.
- Limit downstream queries.
- Limit expensive recursive processing.
Denial of service
- Reject oversized payload before full processing where possible.
- Avoid pathological regex validation.
- Avoid unlimited recursion.
- Avoid unlimited object expansion.
- Bound decompression.
- Bound batch execution.
- Set operation deadlines.
- Monitor validation latency.
Semantic validation
- Validate relationships between fields.
- Validate start <= end.
- Validate quantities against business rules.
- Validate referenced resources.
- Validate allowed state transitions.
- Validate tenant relationships.
- Keep structural and semantic errors distinguishable.
Authorization
- Never trust isAdmin from input.
- Never trust ownerId as proof of ownership.
- Resolve authenticated identity server-side.
- Apply authorization after parsing and validation.
- Check resource-level permissions.
- Avoid mass assignment of privileged fields.
Deserialization
- Deserialize into dedicated request types.
- Avoid generic map-to-domain-model copying.
- Explicitly map supported fields.
- Ignore or reject unsupported fields according to policy.
- Avoid arbitrary runtime type activation.
- Validate before constructing privileged objects.
- Keep domain constructors defensive.
Mass assignment
- Allowlist writable fields.
- Keep role server-controlled.
- Keep verification state server-controlled.
- Keep billing plan server-controlled unless endpoint explicitly owns it.
- Separate admin update models.
- Test unexpected privileged properties.
Enums
- Validate allowed values.
- Decide forward-compatibility behavior.
- Do not silently map unknown values to a privileged default.
- Consider Unknown variant for tolerant consumers.
- Document case sensitivity.
- Test future values.
Defaults
- Apply defaults deliberately.
- Distinguish missing from explicit null.
- Avoid security-sensitive defaults.
- Document whether server or schema applies default.
- Do not assume annotation keywords automatically mutate instance data.
- Test omitted values.
Schema validation
- Validate before business logic.
- Return structured issues.
- Include JSON path.
- Include machine-readable rule code.
- Avoid returning internal schema internals unnecessarily.
- Avoid echoing secrets.
- Keep error order stable when client depends on it.
Error responses
- Distinguish malformed_json.
- Distinguish validation_failed.
- Distinguish unauthorized.
- Distinguish forbidden.
- Distinguish conflict.
- Distinguish internal failure.
- Return safe message.
- Include correlation ID for server errors.
Logging
- Log request ID.
- Log endpoint.
- Log schema version.
- Log validation code.
- Log failing path where safe.
- Avoid logging full secret-bearing payload.
- Redact credentials.
- Avoid duplicate error logging.
Privacy
- Minimize captured payloads.
- Redact passwords.
- Redact tokens.
- Redact payment data.
- Redact sensitive personal data.
- Apply retention rules.
- Restrict access to diagnostic payloads.
Schema versioning
- Version breaking contracts.
- Document compatible additions.
- Plan field removal.
- Avoid changing field meaning silently.
- Keep historical event schemas where replay is required.
- Test old producer with new consumer.
- Test new producer with old consumer where supported.
Backward compatibility
- Adding optional field is often easier than removing required field.
- Keep default behavior stable.
- Avoid changing types.
- Avoid changing units.
- Avoid narrowing enums without migration.
- Test deployed clients.
Forward compatibility
- Decide whether unknown fields are tolerated.
- Decide how unknown enum values behave.
- Avoid interpreting unknown values as privileged states.
- Preserve extension fields only when architecture requires it.
API gateways
- Align gateway parsing with application parsing.
- Align duplicate-key behavior.
- Align size limits.
- Align schema version.
- Do not let gateway accept payload application interprets differently.
- Test bypass paths.
Message queues
- Validate messages at consumer boundary.
- Include schema version.
- Limit message size.
- Define retry / dead-letter policy separately.
- Reject poisoned malformed messages safely.
- Avoid infinite retry on validation errors.
- Preserve safe diagnostic context.
Webhooks
- Authenticate webhook independently.
- Verify signature over correct raw representation.
- Parse only after required authenticity checks according to provider protocol.
- Validate schema after parsing.
- Handle unknown event types deliberately.
- Make processing idempotent.
- Limit payload size.
Configuration JSON
- Validate at startup.
- Reject unknown security-sensitive configuration.
- Validate paths and URLs.
- Validate numeric ranges.
- Fail clearly on invalid required configuration.
- Avoid silently ignoring misspelled critical settings.
JSON files
- Define encoding.
- Handle malformed files clearly.
- Validate schema.
- Use atomic writes where configuration/state integrity matters.
- Do not partially trust file because parser succeeded.
- Protect permissions separately.
Database JSON columns
- Do not treat stored JSON as automatically trusted forever.
- Validate before initial storage.
- Consider schema changes over time.
- Validate when reading legacy documents if assumptions changed.
- Add database constraints where practical.
- Index only fields with defined semantics.
Caching
- Validate external cache content where corruption or version drift is possible.
- Include cache schema version.
- Avoid interpreting stale structure as current model.
- Handle missing fields.
- Rebuild incompatible cache entries safely.
Testing
- Test valid minimum payload.
- Test valid maximum payload.
- Test missing required property.
- Test null.
- Test wrong type.
- Test empty string.
- Test unknown property.
- Test duplicate key.
- Test invalid enum.
- Test huge number.
- Test large array.
- Test deep nesting.
Negative tests
- Prove invalid payloads fail.
- Assert error path.
- Assert error code.
- Assert no business side effect occurred.
- Assert no database write occurred when validation fails.
- Assert privileged fields cannot be mass-assigned.
Boundary tests
- Test raw HTTP body.
- Test content type.
- Test parser.
- Test validator.
- Test semantic layer.
- Test authorization.
- Test typed mapper.
- Test database constraint.
- Test response mapping.
Property-based testing
- Generate random JSON values.
- Generate unexpected root types.
- Generate missing fields.
- Generate extra fields.
- Generate boundary numbers.
- Generate long strings.
- Generate nested arrays.
- Verify validator never crashes.
Fuzzing
- Fuzz parser boundary.
- Fuzz schema validator where appropriate.
- Include malformed UTF-8 at byte boundary.
- Include unusual escapes.
- Include deep nesting.
- Include duplicate fields.
- Monitor CPU and memory.
- Treat crashes and excessive latency as bugs.
Observability
- Track malformed JSON rate.
- Track validation failure rate.
- Track failures by field.
- Track failures by client version.
- Track rejected oversized requests.
- Track validation latency.
- Track unknown-field frequency.
- Track schema-version mismatches.
Debugging
- Capture exact safe reproduction.
- Identify transport boundary.
- Verify encoding.
- Verify raw syntax.
- Check duplicate keys.
- Check root type.
- Run schema validator.
- Inspect failing path.
- Check semantic rule.
- Check authorization.
- Check schema version.
Regression fixtures
- Keep original failing payload in sanitized form.
- Keep expected validation result.
- Test parser and schema together.
- Test gateway and application behavior together.
- Include security-sensitive duplicate-key fixture.
- Include large-number fixture.
- Include unknown-property fixture.
Code review
- Where does parsing occur?
- What size limit applies before parsing?
- Are duplicate keys handled?
- Which schema draft is used?
- Is type validation explicit?
- Are null and missing distinguished?
- What happens to unknown properties?
- Are numbers safe for their domain?
- Are semantic rules separate?
- Is authorization server-side?
- Are only allowed fields mapped?
- Are validation errors safe?
Final review
- Does syntax parsing happen at a clear boundary?
- Is request size limited before expensive processing?
- Is the root JSON type validated?
- Are required fields explicit?
- Are nullability rules explicit?
- Are unknown-field rules documented?
- Is duplicate-key behavior safe and consistent?
- Are numeric precision assumptions tested?
- Are dates treated as validated strings rather than magical JSON primitives?
- Are arrays and strings bounded?
- Is nesting bounded?
- Is JSON Schema dialect explicit?
- Is format behavior verified for the selected validator?
- Are structural and semantic validation separate?
- Is authorization independent from client-controlled JSON?
- Does deserialization map only supported fields?
- Are privileged fields protected from mass assignment?
- Are schemas versioned when contracts change?
- Are errors machine-readable and safe?
- Are secrets excluded from logs and responses?
- Are malformed, adversarial, and boundary payloads covered by tests?
- Can a syntactically valid but hostile payload reach expensive business logic?
15. FAQ
Is valid JSON automatically safe to use?
No. Valid JSON only satisfies the syntax rules. The application still needs structural validation, semantic validation, authorization, resource limits, and safe mapping into internal data structures.
What does JSON Schema validate?
JSON Schema can define structural constraints including JSON types, required properties, arrays, string lengths, number ranges, allowed values, property rules, and schema composition. Business rules that depend on application state usually still belong in application code.
Should APIs use additionalProperties false?
It can be useful for strict request contracts because it catches unknown fields and misspellings. However, strict rejection can reduce forward compatibility. Choose the policy based on the API's evolution strategy and test older and newer clients accordingly.
Why are duplicate JSON keys dangerous?
Different parsers can interpret duplicate object names differently. If a gateway, validator, signature checker, and application disagree about which value wins, an attacker may exploit that inconsistency. Rejecting duplicate names is a strong defensive policy for sensitive inputs.
Does JSON have date, UUID, or decimal types?
No. JSON's primitive data model contains strings, numbers, booleans, null, arrays, and objects. Dates and UUIDs are normally represented as strings and validated according to application or schema rules. Exact decimal semantics also require an explicit representation strategy.
Why should JSON body size be limited before schema validation?
Because parsing and buffering already consume resources. Waiting until schema validation to reject a massive payload may allow excessive memory or CPU use before the validator has a chance to apply field limits.
Should I trust a JSON object after it passes schema validation?
Trust it only for the properties the schema actually guarantees. You may still need semantic rules, authorization, database checks, normalization, and explicit mapping into a domain model before executing business logic.
Key terms (quick glossary)
- JSON
- A text-based data interchange format representing strings, numbers, booleans, null, arrays, and objects.
- JSON parser
- Software that reads JSON syntax and converts it into a runtime data representation.
- JSON Schema
- A schema language for describing and validating constraints on JSON instance data.
- Structural validation
- Checking whether input has the required shape, types, properties, arrays, lengths, ranges, and similar data-level constraints.
- Semantic validation
- Checking whether individually valid values make sense together and obey application or domain rules.
- Defensive parsing
- Processing input under the assumption that malformed, unexpected, oversized, ambiguous, or hostile data can reach the parser.
- Duplicate key
- An object member name appearing more than once in the same JSON object, which can produce inconsistent interpretations across implementations.
- additionalProperties
- A JSON Schema keyword controlling validation of object properties that are not matched by properties or patternProperties in the applicable schema object.
- unevaluatedProperties
- A JSON Schema keyword that applies a schema to object properties not successfully evaluated by relevant adjacent schema keywords.
- format
- A JSON Schema keyword for semantic format annotations or assertions such as certain date, URI, or email-related string formats, depending on the selected vocabulary and validator behavior.
- Mass assignment
- Copying incoming fields automatically into an internal object in a way that may accidentally expose properties the caller should not control.
- Schema dialect
- The specific JSON Schema rules and vocabularies under which a schema is intended to be interpreted.
- Validation path
- The location within a JSON instance associated with a validation failure, such as /items/3/quantity.
- Backward compatibility
- The ability of newer software or schemas to continue supporting data produced according to an older contract.
- Forward compatibility
- The ability of older consumers to tolerate some data produced by a newer contract, such as intentionally ignored extension properties.
Worth reading
Recommended guides from the category.