A powerful, generic JSON-LD builder with comprehensive entity and property filtering capabilities. Provides both immutable configuration building and mutable graph processing with a fluent interface.
# Using pnpm (recommended)
pnpm add jsonldjs
# Using npm
npm install jsonldjs
# Using yarn
yarn add jsonldjs- Configuration-First Design: Separate immutable configuration from graph processing for maximum flexibility
- Fluent Interface: Chainable methods for building complex filtering logic
- Property-Level Filtering: Filter properties by entity IDs or types
- Subgraph Extraction: Extract connected subgraphs with reference following
- Runtime Overrides: Apply configuration and then override at runtime
- Type Safety: Full TypeScript support with proper type inference
- Extensibility: Custom pipes and transformation functions
import { createJsonLdBuilder } from 'jsonldjs';
import { jsonldGraph } from '@/data/jsonld';
// Simple filtering
const result = createJsonLdBuilder()
.baseGraph(jsonldGraph)
.includeTypes(['Organization', 'Person'])
.excludeTypes(['ImageObject'])
.maxEntities(10)
.build({
prettyPrint: true,
withScriptTag: true,
scriptId: 'json-ld',
});import { createJsonLdBuilder, createJsonLdConfig } from 'jsonldjs';
// Create reusable configurations
const globalConfig = createJsonLdConfig()
.baseGraph(jsonldGraph)
.includeIds(['org:hyperweb', 'website:hyperweb.io'])
.filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] });
// Extend configurations immutably
const homeConfig = globalConfig.excludeTypes(['ImageObject']);
const blogConfig = globalConfig.includeTypes(['Article']);
// Use configurations
const result = createJsonLdBuilder()
.mergeConfig(homeConfig.getConfig())
.excludeIds(['runtime:override']) // Runtime overrides
.build({ prettyPrint: true });All configuration methods merge by default instead of replacing. This provides predictable behavior across all methods:
const config = createJsonLdConfig()
.includeIds(['a', 'b'])
.includeIds(['c', 'd']) // Result: ['a', 'b', 'c', 'd']
.includeTypes(['Person'])
.includeTypes(['Organization']); // Result: ['Person', 'Organization']When you need to replace instead of merge, use the clear methods:
const config = createJsonLdConfig()
.includeIds(['old1', 'old2'])
.clearIds() // Clear both includeIds and excludeIds
.includeIds(['new1', 'new2']); // Result: ['new1', 'new2']-
clearIds()- Clears bothincludeIdsandexcludeIds -
clearTypes()- Clears bothincludeTypesandexcludeTypes -
clearPropertyRequirements()- Clears bothrequiredPropertiesandexcludeEntitiesWithProperties -
clearPropertyFilters()- Clears bothpropertyFiltersByIdsandpropertyFiltersByTypes -
clearSubgraph()- ClearssubgraphRoots -
clearAll()- Clears entire configuration (exceptbaseGraph)
Creates a new immutable configuration builder.
const config = createJsonLdConfig()
.baseGraph(graph)
.includeIds(['org:hyperweb'])
.excludeTypes(['ImageObject']);Creates a new builder that extends the configuration builder with graph processing capabilities.
const builder = createJsonLdBuilder().baseGraph(graph).mergeConfig(config);All methods are inherited by the builder from the configuration builder:
-
.includeIds(ids: string[])- Include entities with these IDs (merges with existing) -
.excludeIds(ids: string[])- Exclude entities with these IDs (merges with existing) -
.includeTypes(types: string[])- Include these entity types (merges with existing) -
.excludeTypes(types: string[])- Exclude these entity types (merges with existing) -
.customFilter(fn: JsonLdFilter)- Apply custom filter function -
.maxEntities(max: number)- Limit maximum number of entities -
.requiredProperties(props: string[])- Include entities with these properties (merges with existing) -
.excludeEntitiesWithProperties(props: string[])- Exclude entities with these properties (merges with existing)
-
.clearIds()- Clear both includeIds and excludeIds -
.clearTypes()- Clear both includeTypes and excludeTypes -
.clearPropertyRequirements()- Clear both requiredProperties and excludeEntitiesWithProperties -
.clearPropertyFilters()- Clear both propertyFiltersByIds and propertyFiltersByTypes -
.clearSubgraph()- Clear subgraphRoots -
.clearAll()- Clear entire configuration (except baseGraph)
-
.mergeConfig(config: JsonLdConfig)- Merge with another complete configuration -
.mergeFilters(filters: JsonLdFilterOptions)- Merge only the filters part of another configuration
Available in both config builder and main builder - These methods work the same way in both classes.
// Config builder usage
const baseConfig = createJsonLdConfig().includeTypes(['Person']);
const otherConfig = createJsonLdConfig()
.includeTypes(['Organization'])
.excludeIds(['test'])
.getConfig();
const merged = baseConfig.mergeConfig(otherConfig);
// Result: includeTypes: ['Person', 'Organization'], excludeIds: ['test']
// Main builder usage (processes graph immediately)
const result = createJsonLdBuilder()
.baseGraph(graph)
.includeTypes(['Person'])
.mergeConfig(otherConfig)
.build({ prettyPrint: true });
// Merge only filters
const baseConfig = createJsonLdConfig().includeTypes(['Person']).addEntities([entity]);
const otherFilters = { includeTypes: ['Organization'], maxEntities: 10 };
const merged = baseConfig.mergeFilters(otherFilters);
// Result: includeTypes: ['Person', 'Organization'], maxEntities: 10, additionalEntities preserved-
.filterPropertiesByIds(entityIds, rule)- Filter properties for specific entity IDs -
.filterPropertiesByTypes(entityTypes, rule)- Filter properties for specific entity types
// Filter properties by entity ID
.filterPropertiesByIds(['org:hyperweb'], {
exclude: ['subjectOf', 'member']
})
// Filter properties by entity type
.filterPropertiesByTypes(['Article'], {
include: ['headline', 'author', 'datePublished']
})-
.baseGraph(graph: JsonLdGraph)- Set the base graph to process -
.subgraph(rootIds: string[])- Extract subgraph starting from these root IDs -
.addEntities(entities: JsonLdEntity[])- Add additional entities -
.pipe(fn: PipeFunction)- Add custom transformation function
-
.getCurrentGraph()- Get the current graph state -
.build(options?: BuildOptions)- Build the final JSON-LD output
interface BuildOptions {
prettyPrint?: boolean; // Pretty-print JSON output (default: true)
contextUrl?: string; // Custom context URL (default: 'https://schema.org')
withScriptTag?: boolean; // Wrap in script tag (default: false)
scriptId?: string; // Script tag ID
}The builder implements three distinct filtering paths based on configuration:
-
Subgraph Mode: When
subgraph()is used, property filtering is applied during traversal -
IncludeIds Mode: When
includeIds()is used, entities are filtered first, then additional filters applied - Global Mode: Property filtering is applied first, then entity filtering
// Subgraph mode - follows references with property filtering
const result = createJsonLdBuilder()
.baseGraph(graph)
.subgraph(['org:hyperweb'])
.filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] })
.build();
// IncludeIds mode - simple entity filtering
const result = createJsonLdBuilder()
.baseGraph(graph)
.includeIds(['org:hyperweb', 'person:john'])
.excludeTypes(['ImageObject'])
.build();const result = createJsonLdBuilder()
.baseGraph(graph)
.includeTypes(['Person'])
.pipe((graph) =>
graph.map((entity) => ({
...entity,
processed: true,
}))
)
.pipe((graph) => graph.filter((entity) => entity.name))
.build();// Base configuration
const baseConfig = createJsonLdConfig()
.baseGraph(jsonldGraph)
.filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] });
// Page-specific configurations
const homeConfig = baseConfig.excludeTypes(['ImageObject']);
const blogConfig = baseConfig.includeTypes(['Article']);
const personConfig = baseConfig.includeTypes(['Person', 'Organization']);
// Use with different base graphs
const articlesConfig = baseConfig.baseGraph(articlesGraph);The JSON-LD builder processes options in a specific order defined by the processGraph method. Understanding this order is crucial for predicting the final output when multiple filtering options are applied.
The builder processes options in the following sequential layers:
- Validates that a base graph is provided
- Checks for critical configuration errors that would break processing
- Throws errors if validation fails
-
Condition: Only when
subgraphRootsare configured via.subgraph(rootIds) - Process: Extracts connected subgraphs starting from root entities
- Property Filtering: Applied during subgraph traversal for optimal performance
- Result: Property filters are marked as applied to avoid duplicate processing
// Example: Subgraph extraction with property filtering
const result = createJsonLdBuilder()
.baseGraph(graph)
.subgraph(['org:hyperweb']) // Layer 1: Extract subgraph
.filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] }) // Applied during traversal
.build();- Condition: When entity filters are configured (includeIds, excludeIds, includeTypes, etc.)
-
Sub-step 3a - Property Filtering: Applied first if not already done in Layer 1
- Filters properties based on entity IDs or types
- Uses
filterGraphProperties()function
-
Sub-step 3b - Entity Filtering: Applied after property filtering
- Filters entire entities based on ID, type, and other criteria
- Uses
filterJsonLdGraph()function
// Example: Property filtering followed by entity filtering
const result = createJsonLdBuilder()
.baseGraph(graph)
.filterPropertiesByTypes(['Article'], { include: ['headline', 'author'] }) // 3a: Property filtering
.includeTypes(['Article', 'Person']) // 3b: Entity filtering
.excludeIds(['unwanted:id']) // 3b: Additional entity filtering
.build();-
Condition: When
populateConfigis set - Process: Applies population rules to add related entities
-
Function: Uses
applyPopulateConfig()
-
Condition: When additional entities are specified via
.addEntities() - Process: Appends additional entities to the graph
- Note: These entities bypass all previous filtering
-
Condition: When custom pipes are added via
.pipe(fn) - Process: Applies custom transformation functions in the order they were added
- Note: This is the final processing step before output
// Example: Custom pipes applied last
const result = createJsonLdBuilder()
.baseGraph(graph)
.includeTypes(['Person'])
.pipe((graph) => graph.map((entity) => ({ ...entity, processed: true }))) // Applied last
.pipe((graph) => graph.filter((entity) => entity.name)) // Applied after previous pipe
.build();-
Property Filters Before Entity Filters: Property filtering always happens before entity filtering (except in subgraph mode where they're combined)
-
Subgraph Mode Optimization: When using subgraphs, property filtering is applied during traversal for better performance
-
Single Property Filter Application: Property filters are only applied once to avoid duplicate processing
-
Additive Additional Entities: Entities added via
.addEntities()are appended after all filtering -
Sequential Pipe Execution: Custom pipes are executed in the order they were added
- Immutable Configurations: Each configuration method returns a new object, enabling safe reuse
-
Lazy Evaluation: Graph processing only occurs when
build()orgetCurrentGraph()is called - Efficient Filtering: Uses optimized filtering paths based on configuration type
- Memory Management: Avoids unnecessary intermediate copies of large graphs
- Clone the repository:
git clone https://github.com/constructive-io/dev-utils.git- Install dependencies:
cd dev-utils
pnpm install
pnpm build- Test the package of interest:
cd packages/<packagename>
pnpm test:watchBuilt for developers, with developers.
👉 https://launchql.com | https://hyperweb.io
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.