Skip to content

Menu

Design a JSON Schema for product extraction

Build a product JSON Schema with explicit null, text and list rules, then validate every captured row using a reproducible Node.js example.

By Scraipe editorial. Checked 2026-10-18.

Start with the product record your application needs

Design a product extraction schema around a specific consumer: the fields it needs, their meanings and the cases it must reject. A JSON object with plausible property names is not yet a reliable catalog record. Decide whether a price is displayed text or a normalized amount, whether a missing value may be null, and whether a field contains one value or a list.

This guide builds a small, runnable contract for product titles, displayed prices and tag lists. It validates captured data locally with a real JSON Schema validator. The fixtures are invented; they are not customer data or a live extraction result. The example requires Node 22 or later and was tested with Node 22.23.2 and Ajv 8.20.0 on 18 October 2026.

Make the field decisions explicit

FieldContractReason
titleRequired nonblank stringA catalog row without an identifiable title needs inspection.
priceTextRequired nonblank string or explicit nullRetain currency and formatting; null means the value was unavailable, not zero.
tagsRequired array of nonblank strings; empty allowedA product may have several labels or none. One label is still an array.

These are teaching choices, not universal product fields. If your source provides a SKU, retain and validate it under a separate field. Do not manufacture a SKU from a row index or assume titles are unique. Add availability only after deciding how to distinguish “out of stock” from “availability not captured.”

Keeping priceText as text avoids silently turning AUD 12.50 into an amount with an unknown currency. A later, separately tested transformation can produce normalized amounts. Neither JSON Schema nor a descriptive field hint performs currency conversion.

Write a schema for the whole result array

The complete schema declares draft-07 and includes this record shape:

json

{
  "type": "object",
  "properties": {
    "title": { "type": "string", "minLength": 1, "pattern": "\\S" },
    "priceText": {
      "type": ["string", "null"],
      "minLength": 1,
      "pattern": "\\S"
    },
    "tags": {
      "type": "array",
      "items": { "type": "string", "minLength": 1, "pattern": "\\S" }
    }
  },
  "required": ["title", "priceText", "tags"],
  "additionalProperties": false
}

The downloadable file wraps that record in type: "array", with minItems: 1 and maxItems: 1000. Those are this example consumer's batch rules, not Scraipe account limits. If an empty catalog is valid for your application, deliberately change the rule and its tests after checking the collection scope.

properties specifies how a present property is validated; it does not make that property mandatory. required checks presence. priceText: null is therefore allowed, but an absent priceText key fails. For strings, minLength: 1 rejects empty text while pattern: "\\S" requires a non-whitespace character. The string constraints do not reject the explicitly permitted null.

additionalProperties: false rejects unexpected fields so a changed payload is reviewed instead of silently discarded. It is a policy choice: when you intentionally extend the contract, version the schema and update downstream storage. The description annotations in the full file explain field meaning; they do not prove that the extracted value matches that meaning.

Run the validator and failure cases

Download these six files into one fresh directory:

Then run:

bash

npm ci --ignore-scripts --no-fund --no-audit
npm test
node validate-products.mjs products.fixture.json

The install downloads the pinned validator dependencies; the tests and validator make no network requests and spend no Scraipe credits. The expected test result is ten passes. The fixture command prints:

json

{ "valid": true, "rows": 2, "issueCount": 0, "issues": [] }

One fixture product has priceText: "AUD 12.50" and two tags. The other has an explicit null price and no tags. Change a price to numeric 0: the report becomes invalid rather than converting it into text. Delete priceText instead of setting it to null, replace the tags array with a string, or add an unexpected property to exercise other failures.

The CLI exits 0 for valid data, 1 for a validation report containing issues and 2 for an unreadable, oversized or syntactically invalid capture. It expects the product array itself, not the API response envelope. Reports include a row count, total issue count and at most twenty path/keyword diagnostics. The full schema is applied to every record, including records after the fifth; errors are not evidence that partial import is safe.

The Ajv configuration disables coercion, default insertion and additional-property removal. Input data remains unchanged. A default annotation is not a source value, and validator-specific mutation options should not quietly fill a missing price. See the missing-field guide for the difference between absence, null, blank text and a legitimate zero.

Use the schema with the extraction API

For a real request, the API reference accepts a schema object as an alternative to fields on POST /v1/scrape. Supply the contents of the downloaded schema, a permitted public source URL and your server-side key with the scrape scope. For example, the shape of the request is:

javascript

const requestBody = {
  url: sourceUrl,       // A real page you are permitted to process
  schema: productSchema, // Parsed products.schema.json
  maxTokens: 2000,
  egress: 'direct',
};

This is a request-body recipe, not a completed extraction. Keep the API origin separate from sourceUrl, keep credentials out of browser code, and begin with a small page in the playground. Compare titles, displayed prices and tag membership with the source rather than relying on a schema pass alone.

Before saving response.data to a file for this validator, inspect the envelope: success, truncation, cursor, returned/total counts and the request receipt. A response that is incomplete or failed must not be made acceptable by validating its surviving rows. The Python workflow demonstrates those envelope checks for one initial request. A continuation requires its own bounded accumulation rules.

Keep extraction checks and consumer validation separate

The current Scraipe code uses a record-level validation check during extraction. It does not enforce the entire top-level array contract there, and its runtime conformance check samples up to five records. It also permits null for optional scalar fields. A schema that its internal validator cannot compile is treated as unavailable for that check, not as proof that the data satisfies it. These implementation boundaries are why this guide validates the complete captured array independently.

The example uses draft-07, supported by Ajv's default class. Other dialects and keywords require compatible validator configuration; do not assume that declaring a 2020-12 dialect makes every extractor or validator enforce it. Keep the schema you test, the validator configuration and the deployed extraction behavior under review together.

A local source probe ran a handwritten selector specification against invented HTML and obtained a title, null missing price and two tag strings that passed the record contract. It also checked rejection of invalid required types. That proves the local selector/validator behavior for this fixture, not that the model will compile every page correctly or that the hosted service extracted a real catalog.

Inspect cost and semantic accuracy before scaling

A schema describes the data contract; it does not guarantee free compilation, successful access or complete site coverage. This recipe selects direct egress and does not opt into residential fallback, so some pages may fail. Inspect the request receipt for compilation, repair and network charges. Changing a field's structure can affect extractor reuse; a wording change is not a reliable way to request a fresh operation.

Finally, test a representative set of permitted pages and manually compare important values. A wrong product's valid-looking price can satisfy the schema. Keep source URL, retrieval context and receipt with each capture, store rejected records separately, and investigate systematic failures before retrying. The CLI reads small captures into memory and uses ordinary JSON parsing; it is not a streaming importer or a duplicate-member detector. Production ingestion needs explicit size, transport, provenance and retry policies around these record checks.

Sources and verification

Try the next step

Try the playground

Continue building