Working with JSON Safely: Validation, Schemas, and Defensive Parsing

Last updated: ⏱ Reading time: ~20 minutes

AI-assisted guide Curated by Norbert Sowinski

Share this guide:

Diagram-style illustration of safe JSON processing showing incoming bytes, parsing, duplicate-key handling, structural validation, JSON Schema, semantic validation, business rules, typed application objects, size and depth limits, error responses, and defensive API boundaries

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)

Safe JSON parsing pipeline showing request size limits, byte decoding, syntax parsing, duplicate-key handling, structural schema validation, semantic validation, authorization, typed mapping, business logic, safe validation errors, logging, and rejection paths

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:

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 validation layers showing JSON syntax, root type, required properties, property types, numeric and string constraints, arrays and nested objects, additional or unevaluated properties, format annotations, semantic application validation, authorization, and database constraints

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)

Defensive JSON debugging workflow showing raw request capture, transport and encoding verification, body-size check, syntax parsing, duplicate-key detection, root-type validation, JSON Schema validation, semantic rules, authorization, typed mapping, error-path identification, schema version checks, regression fixtures, and monitoring

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.

Found this useful? Share this guide: