@jsonql-standard/jsonql-parser
v1.0.0
Published
A library to parse and validate JSONQL queries against the schema, useful for implementations
Maintainers
Readme
jsonql-parser
A TypeScript/JavaScript library to parse and validate JSONQL v1.0 queries against schemas. JSONQL is a secure, lightweight, and polyglot JSON-based query language for filtering, sorting, pagination, and field selection in RESTful APIs.
Website: https://jsonql.org
Specification: github.com/JSONQL-Standard/jsonql-spec
Features
- ✅ Full JSONQL v1.0 specification support
- 🔒 Secure by design (parameterized, no regex injection, no code execution)
- 📝 TypeScript support with full type definitions
- ✔️ Schema validation for fields and relations
- 🏗️ Fluent query builder API
- 🧪 Comprehensive test coverage (76+ tests)
- 🔧 Configurable parsing options (max depth, max limit, field allowlists)
- 📦 Zero dependencies (dev dependencies only)
- 🌐 Framework and database agnostic
Installation
npm install @jsonql-standard/jsonql-parserOr with yarn:
yarn add @jsonql-standard/jsonql-parserQuick Start
Basic Parsing
import { JSONQLParser } from '@jsonql-standard/jsonql-parser';
const parser = new JSONQLParser();
// Parse a JSONQL query
const query = parser.parse({
version: '1.0',
where: {
age: { gte: 18 },
status: { eq: 'active' }
},
sort: ['name', '-created_at'],
limit: 10
});
console.log(query);With Validation
import { JSONQL } from 'jsonql-parser';
// Define your schema
const schema = {
users: {
fields: {
id: { type: 'number', required: true },
name: { type: 'string', required: true },
email: { type: 'string', required: true },
age: { type: 'number' },
status: { type: 'string' }
},
relations: {
posts: { type: 'hasMany', target: 'posts' }
}
}
};
// Create JSONQL instance with schema
const jsonql = new JSONQL(schema, 'users');
// Parse and validate
const result = jsonql.parseAndValidate({
version: '1.0',
fields: ['id', 'name', 'email'],
where: {
age: { gte: 18 }
},
limit: 10
});
if (result.validation.valid) {
console.log('Query is valid!', result.query);
} else {
console.error('Validation errors:', result.validation.errors);
}Query Builder
import { JSONQLQueryBuilder, field, gte, eq, and } from 'jsonql-parser';
const builder = new JSONQLQueryBuilder();
const query = builder
.fields('id', 'name', 'email')
.where(
and(
field('age', gte(18)),
field('status', eq('active'))
)
)
.sort('name', '-created_at')
.limit(10)
.skip(0)
.include('posts')
.build();
console.log(JSON.stringify(query, null, 2));JSONQL v1.0 Query Structure
{
"version": "1.0",
"where": { /* conditions */ },
"sort": "field" | ["field", "-field"],
"limit": 100,
"skip": 0,
"fields": ["id", "name"],
"include": ["author", "tags"]
}Supported Operators
| Operator | Description | Example |
|----------|-------------|---------|
| eq | Equal to | { "status": { "eq": "active" } } |
| ne | Not equal to | { "status": { "ne": "deleted" } } |
| gt | Greater than | { "age": { "gt": 18 } } |
| gte | Greater than or equal | { "age": { "gte": 18 } } |
| lt | Less than | { "price": { "lt": 100 } } |
| lte | Less than or equal | { "price": { "lte": 100 } } |
| in | In array | { "id": { "in": [1, 2, 3] } } |
| nin | Not in array | { "status": { "nin": ["spam", "deleted"] } } |
| contains | Contains substring | { "name": { "contains": "john" } } |
| starts | Starts with prefix | { "email": { "starts": "admin" } } |
| ends | Ends with suffix | { "email": { "ends": "@company.com" } } |
Logical Operators
// AND
{
"and": [
{ "age": { "gte": 18 } },
{ "status": { "eq": "active" } }
]
}
// OR
{
"or": [
{ "role": { "eq": "admin" } },
{ "role": { "eq": "moderator" } }
]
}
// NOT
{
"not": {
"status": { "eq": "deleted" }
}
}Field-to-Field Comparisons
// Compare two fields
{
"where": {
"price": { "gt": { "field": "cost" } }
}
}
// With nested fields (requires include)
{
"where": {
"startDate": { "lt": { "field": "author.createDate" } }
},
"include": ["author"]
}API Reference
JSONQLParser
const parser = new JSONQLParser(options?);
// Options
interface JSONQLParserOptions {
maxNestingDepth?: number; // Default: 5
maxLimit?: number; // Default: 1000
allowedFields?: string[]; // Field allowlist
allowedIncludes?: string[]; // Relation allowlist
}
// Methods
parser.parse(input: string | object): JSONQLQuery
parser.stringify(query: JSONQLQuery): stringJSONQLValidator
const validator = new JSONQLValidator(schema, tableName);
validator.validate(query: JSONQLQuery): ValidationResult
validator.setSchema(schema: JSONQLSchema, tableName: string): void
validator.getSchema(): JSONQLSchema
validator.getTableName(): stringJSONQLQueryBuilder
const builder = new JSONQLQueryBuilder();
// Fluent API
builder
.where(condition: JSONQLWhere)
.andWhere(condition: JSONQLWhere)
.orWhere(condition: JSONQLWhere)
.sort(...fields: string[])
.limit(n: number)
.skip(n: number)
.fields(...fields: string[])
.include(...relations: string[])
.build(): JSONQLQuery
.reset(): thisHelper Functions
// Condition helpers
import {
field, eq, ne, gt, gte, lt, lte,
inArray, nin, contains, starts, ends,
fieldRef, and, or, not
} from 'jsonql-parser';
// Build conditions
field('age', gte(18))
// => { "age": { "gte": 18 } }
and(
field('age', gte(18)),
field('status', eq('active'))
)
// => { "and": [...] }
// Field reference
field('price', gt(fieldRef('cost')))
// => { "price": { "gt": { "field": "cost" } } }Examples
Complex Query
const query = {
version: '1.0',
fields: ['id', 'name', 'email'],
where: {
and: [
{ age: { gte: 18 } },
{
or: [
{ role: { eq: 'admin' } },
{ email: { ends: '@company.com' } }
]
},
{ status: { ne: 'deleted' } }
]
},
sort: ['name', '-created_at'],
limit: 10,
skip: 0,
include: ['posts', 'profile']
};
const result = jsonql.parseAndValidate(query);Using Query Builder
import { JSONQLQueryBuilder, field, gte, eq, ends, or, and } from 'jsonql-parser';
const query = new JSONQLQueryBuilder()
.fields('id', 'name', 'email')
.where(
and(
field('age', gte(18)),
or(
field('role', eq('admin')),
field('email', ends('@company.com'))
)
)
)
.sort('name', '-created_at')
.limit(10)
.include('posts', 'profile')
.build();Security Best Practices
- Validate all queries against a schema
- Use field allowlists to restrict accessible fields
- Limit nesting depth (default: 5) to prevent DoS
- Cap limit (default: 1000) to prevent resource exhaustion
- Rate-limit complex queries
- Use parameterized queries when converting to SQL/NoSQL
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Lint
npm run lint
# Format
npm run formatContributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
License
MIT © JSONQL Standard
Related Projects
- jsonql-spec - JSONQL specification
- jsonql.org - Official website
