Wrap an already-collected list of nodes. Prefer the static factories unless
you have pre-filtered nodes of your own. Each node is normalized on the way
in: schema.org enumeration URLs in string values are reduced to their bare
member name (see stripSchemaEnumPrefix), leaving @context intact.
Top-level schema.org nodes to hold.
const data = new SchemaOrgData([
{ '@context': 'https://schema.org', '@type': 'Product' },
]);
StaticfromParse every <script type="application/ld+json"> block under root,
flatten @graph wrappers and arrays, and keep only nodes whose @context
is schema.org.
A document or element subtree to search. Defaults to the
global document in a browser; pass a jsdom/linkedom document in Node.
A SchemaOrgData over the discovered nodes.
import { JSDOM } from 'jsdom';
const { document } = new JSDOM(html).window;
const data = SchemaOrgData.fromDocument(document);
static fromDocument(root: ParentNode = document): SchemaOrgData {
const nodes = parseScripts(root)
.flatMap(collectNodes)
.filter((node) => isSchemaContext(node['@context']));
return new SchemaOrgData(nodes);
}
StaticfromBuild directly from already-parsed object(s), applying the same @graph
flattening and schema.org context filtering as
SchemaOrgData.fromDocument. Useful for tests or when the JSON-LD is
obtained without a DOM.
A single parsed object or an array of them.
A SchemaOrgData over the schema.org nodes found.
const data = SchemaOrgData.fromNodes([
{ '@context': 'https://schema.org', '@type': 'Product', sku: 'CHEM027' },
]);
static fromNodes(input: JsonObject | JsonObject[]): SchemaOrgData {
const nodes = toArray(input)
.flatMap(collectNodes)
.filter((node) => isSchemaContext(node['@context']));
return new SchemaOrgData(nodes);
}
StaticfromDeep-scan an arbitrary object (or array) and collect every sub-object that
carries a schema.org @context. Use this when the JSON-LD is embedded in a
larger app-state blob under unpredictable keys — as many storefronts do —
rather than provided as a clean node. Detection is by @context, not
@type, so unrelated framework types with their own @type are ignored.
Recursion stops at each match: a context-bearing object is taken whole, and its nested typed children (which inherit that context) stay inside it — reach them later with all.
Any parsed JSON value to search.
A SchemaOrgData over every schema.org node found at any depth.
// A Bloomreach/ATG page-state object with schema buried inside:
const data = SchemaOrgData.fromObject(pageState);
data.types(); // ['BreadcrumbList', 'Product']
data.first('Product'); // the nested Product node
static fromObject(data: unknown): SchemaOrgData {
const found: SchemaNode[] = [];
const visit = (value: JsonValue): void => {
if (Array.isArray(value)) {
value.forEach(visit);
return;
}
if (!isPlainObject(value)) {
return;
}
if (isSchemaContext(value['@context'])) {
found.push(value); // take the node whole; don't descend into it
return;
}
for (const key of Object.keys(value)) visit(value[key]);
};
visit(data as JsonValue);
return new SchemaOrgData(found);
}
List the distinct @type values across all top-level nodes.
Each declared top-level type once, in first-seen order.
data.types(); // ['Organization', 'WebPage', 'BreadcrumbList', 'Product']
types(): string[] {
const seen = new Set<string>();
for (const node of this.nodes) {
for (const type of typeList(node)) seen.add(type);
}
return [...seen];
}
Test whether any top-level node declares type.
The schema.org type name to look for (e.g. 'Product').
true if at least one top-level node has that type.
data.has('Product'); // true
data.has('Recipe'); // false
has(type: string): boolean {
return this.nodes.some((node) => typeList(node).includes(type));
}
All top-level nodes declaring type. A node with several types matches each
of them, so it can be returned by more than one get call.
The schema.org type name to filter by.
Matching top-level nodes, in document order (empty if none).
data.get('Product'); // every top-level Product node
data.get('Product').length; // how many there are
get(type: string): SchemaNode[] {
return this.nodes.filter((node) => typeList(node).includes(type));
}
The first top-level node of type, if any.
The schema.org type name to look for.
The first matching node, or undefined when none exists.
data.first('Product')?.sku; // 'CHEM027'
data.first('Recipe'); // undefined
first(type: string): SchemaNode | undefined {
return this.get(type)[0];
}
Depth-first search for every typed object anywhere in the tree, including
nested ones — the Offer inside a Product, or a PropertyValue used as
an identifier. Omit type to collect all typed objects.
Optionaltype: stringOptional schema.org type to filter by; when omitted, every
object carrying an @type is returned.
All matching nodes at any depth, in depth-first document order.
data.all('Offer'); // Offers wherever they are nested
data.all('PropertyValue'); // every identifier-style value pair
data.all(); // all typed objects on the page
all(type?: string): SchemaNode[] {
const found: SchemaNode[] = [];
const visit = (value: JsonValue): void => {
if (Array.isArray(value)) {
value.forEach(visit);
return;
}
if (!isPlainObject(value)) {
return;
}
if ('@type' in value && (type === undefined || typeList(value).includes(type))) {
found.push(value);
}
for (const key of Object.keys(value)) visit(value[key]);
};
this.nodes.forEach(visit);
return found;
}
A type-keyed view of the data: each node's @type becomes the wrapping key
and @type/@context drop out of the body. Arrays whose elements are all
single-typed objects are grouped by type (a BreadcrumbList's items become
{ ListItem: [...] }), and multiple top-level nodes of the same type
collapse into an array under that key. Optimized for readable inspection
rather than programmatic access.
A plain object keyed by schema.org type.
data.toNested().BreadcrumbList.itemListElement.ListItem[0].name; // 'Home'
data.toNested().Product.offers.Offer.price; // '5.8'
toNested(): JsonObject {
const out: JsonObject = {};
for (const node of this.nodes) {
const nested = nestObject(node);
if (!isPlainObject(nested)) continue;
for (const [type, body] of Object.entries(nested)) {
if (out[type] === undefined) {
out[type] = body;
} else {
const existing = out[type];
out[type] = Array.isArray(existing) ? [...existing, body] : [existing, body];
}
}
}
return out;
}
ReadonlynodesTop-level schema.org nodes, in document order, with @type and @context
left intact. @graph wrappers are already flattened into this list.
const data = SchemaOrgData.fromNodes(blocks);
data.nodes.length; // number of top-level entities
data.nodes[0]['@type']; // 'Organization'
readonly nodes: readonly SchemaNode[];
Extracts, normalizes, and queries schema.org JSON-LD found on a page.
Construct it from a DOM (or DOM-like) document with SchemaOrgData.fromDocument, or from already-parsed objects with SchemaOrgData.fromNodes, then use the query helpers (get, first, all) or the type-keyed toNested view.
Example
Source