VSoft.YAML provides powerful JSONPath-style querying capabilities for navigating and filtering YAML documents. This implementation is inspired by the JSONPath specification but adapted specifically for YAML data structures.
The YAMLPathProcessor desgin borrows heavily from Neslib.JSON - extended to support JSonPath style filters.
- Getting Started
- Basic Syntax
- Core Operators
- Filter Expressions
- Advanced Features
- Examples
- Error Handling
JSONPath queries are executed using the public interface methods on IYAMLDocument and IYAMLCollection:
- Document-Level Queries:
var
doc: IYAMLDocument;
matches: IYAMLSequence;
result: IYAMLValue;
found: Boolean;
begin
doc := TYAML.LoadFromString(yamlContent);
// Returns all matches as IYAMLSequence
matches := doc.Query('$.name');
// Returns first match only
found := doc.QuerySingle('$.name', result);
end;- Collection-Level Queries:
var
doc: IYAMLDocument;
products: IYAMLSequence;
matches: IYAMLSequence;
begin
doc := TYAML.LoadFromString(yamlContent);
products := doc.Root.AsMapping.Items['products'].AsSequence;
// Query within a specific collection
matches := products.Query('$[?(@.price > 100)]');
end;Both IYAMLDocument and IYAMLCollection provide:
-
Query(const AExpression: string): IYAMLSequence
Returns all matching values as a sequence -
QuerySingle(const AExpression: string; out AMatch: IYAMLValue): Boolean
Returns the first match and a boolean indicating success
All JSONPath expressions must start with the root operator $.
$- Returns the root document itself
$.property- Access a property by name$.nested.property- Access nested properties$['property']- Access property using bracket notation$["property"]- Access property using double quotes
$.name # Access 'name' property
$.person.address.city # Access nested properties
$['name'] # Single-quoted property name
$["name"] # Double-quoted property name
$['special-key'] # Properties with special characters
$["key with spaces"] # Properties with spaces
$.items[0] # First element (0-based indexing)
$.items[1] # Second element
$.matrix[0][1] # Nested array access
$.items[0,2,4] # Elements at indices 0, 2, and 4
$.data[1,3,5,7] # Multiple specific indices
$.items[1:4] # Elements from index 1 to 3 (4 exclusive)
$.items[2:] # Elements from index 2 to end
$.items[:3] # Elements from start to index 2 (3 exclusive)
$.items[1:10:2] # Elements from 1 to 9 with step 2 (1,3,5,7,9)
$.* # All direct properties
$.person.* # All properties of person object
$.items[*] # All array elements
$.*[*] # All elements of all arrays
$['*'] # All properties (quoted wildcard)
$["*"] # All properties (double-quoted wildcard)
The .. operator recursively searches through all levels of the document:
$..name # All 'name' properties at any level
$..items[0] # First element of any 'items' array
$.store..price # All 'price' properties under 'store'
Filter expressions allow conditional selection using the syntax [?(...)].
$.array[?(condition)]
@- References the current item being evaluated@.property- Access property of current item@.nested.property- Access nested properties of current item
$- References the root document$.property- Access root document properties in filter
"string"or'string'- String literals123,45.67- Numeric literalstrue,false- Boolean literals
==- Equals!=- Not equals>- Greater than>=- Greater than or equal<- Less than<=- Less than or equal
contains- String contains substring=~- Regular expression match (planned)
size- Compare collection sizeempty- Check if collection is emptyin- Check if value is in collection (planned)nin- Check if value is not in collection (planned)
&&- Logical AND||- Logical OR!- Logical NOT
(...)- Parentheses for grouping expressions
$.products[?(@.price > 100)] # Products over $100
$.items[?(@.category == "Electronics")] # Electronics items
$.users[?(@.active == true)] # Active users
$.books[?(@.rating >= 4.5)] # Highly rated books
$.products[?(@.price > 50 && @.inStock == true)] # Expensive in-stock items
$.items[?(@.category == "Books" || @.category == "Media")] # Books or Media
$.users[?(!(@.status == "inactive"))] # Not inactive users
$.products[?(@.category == "Electronics" && (@.price < 100 || @.rating > 4.5))]
# Electronics items that are either cheap or highly rated
$.items[?(@.tags contains "sale" && @.discount > 0.1)]
# Items on sale with significant discount
$.users[?(@.orders size 0)] # Users with no orders
$.categories[?(@.items empty true)] # Empty categories
$.products[?(@.reviews size > 5)] # Products with many reviews
All property names are case-sensitive:
$.Name # Different from $.name
$.USER # Different from $.user
Use bracket notation with quotes for special characters:
$["special-key"] # Hyphenated keys
$["key with spaces"] # Keys with spaces
$["key.with.dots"] # Keys with dots
$["key[with]brackets"] # Keys with brackets
$.store..book[?(@.price < 10)].title # Titles of cheap books anywhere in store
$.users[*].orders[?(@.total > 100)].items # Items from expensive orders of all users
Filters can evaluate truthiness without explicit operators:
$.items[?(@.featured)] # Items where featured is truthy
$.users[?(@.email)] # Users with non-empty email
$.products[?(@.discount)] # Products with any discount
store:
books:
- title: "The Great Gatsby"
author: "F. Scott Fitzgerald"
price: 12.99
category: "Fiction"
inStock: true
rating: 4.5
- title: "To Kill a Mockingbird"
author: "Harper Lee"
price: 14.99
category: "Fiction"
inStock: false
rating: 4.8
electronics:
- name: "Laptop"
price: 899.99
category: "Computing"
inStock: true
rating: 4.2
- name: "Mouse"
price: 25.50
category: "Accessories"
inStock: true
rating: 3.8
customers:
- name: "John Doe"
email: "john@example.com"
active: true
orders:
- id: 1
total: 150.50
- id: 2
total: 75.25
- name: "Jane Smith"
email: "jane@example.com"
active: false
orders: []// Load the YAML document
doc := TYAML.LoadFromString(yamlContent);
// Basic property access
rootValue := doc.Query('$'); // Root document
storeValue := doc.Query('$.store'); // Store object
books := doc.Query('$.store.books'); // All books
firstBook := doc.Query('$.store.books[0]'); // First book
title := doc.Query('$.store.books[0].title'); // Title of first book
names := doc.Query('$.customers[*].name'); // All customer names// Price-based filtering
cheapBooks := doc.Query('$.store.books[?(@.price < 15)]'); // Cheap books
inStockElectronics := doc.Query('$.store.electronics[?(@.inStock == true)]'); // In-stock electronics
// Status filtering
activeCustomers := doc.Query('$.customers[?(@.active == true)]'); // Active customers
inStockItems := doc.Query('$.store..inStock[?(@ == true)]'); // All in-stock items
// Collection size filtering
customersWithOrders := doc.Query('$.customers[?(@.orders size > 0)]'); // Customers with orders// Multi-criteria filtering
highRatedTitles := doc.Query('$..books[?(@.rating > 4.6)].title'); // Titles of highly-rated books
// Logical combinations
activeMultiOrderCustomers := doc.Query('$.customers[?(@.active && @.orders size > 1)].name'); // Active customers with multiple orders
// Recursive search with filtering
expensiveItems := doc.Query('$.store..*[?(@.price > 100)]'); // All expensive items in store- Error:
EYAMLPathError- "A YAML path must start with a root ($) operator" - Cause: Path doesn't start with
$ - Fix: Always begin paths with
$
- Error:
EYAMLPathError- "Operator in YAML path must start with dot (.) or bracket ([)" - Cause: Invalid character after root or property
- Fix: Use
.for properties or[...]for indexing
- Error:
EYAMLPathError- "Missing close bracket (]) in YAML path" - Cause: Unmatched brackets
- Fix: Ensure all
[have corresponding]
- Error:
EYAMLPathError- "Quote mismatch in YAML path" - Cause: Mixed or unmatched quotes in bracket notation
- Fix: Use consistent quote types (
'...'or"...")
- Error:
EYAMLPathError- "Invalid filter expression in YAML path" - Cause: Malformed filter syntax
- Fix: Check filter syntax and parentheses balance
- Always start with
$- Root operator is mandatory - Use quotes for special characters -
$["special-key"]not$.special-key - Test complex filters incrementally - Build up complex expressions step by step
- Handle empty results - Check sequence count before accessing items
- Use QuerySingle for single values - More efficient when you only need the first match
- Cache document references - Reuse IYAMLDocument instances when possible
- Document.Query() vs Collection.Query(): Use Collection.Query() to search within specific collections
- QuerySingle vs Query: Use QuerySingle when you only need the first result
- Simple paths: Faster than complex filter expressions
- Specific indices: More efficient than wildcards when possible
- Early filtering: Apply filters early to reduce processing scope
var
doc: IYAMLDocument;
matches: IYAMLSequence;
singleResult: IYAMLValue;
found: Boolean;
begin
doc := TYAML.LoadFromString(yamlContent);
// Multiple results
matches := doc.Query('$.products[*].name');
for i := 0 to matches.Count - 1 do
WriteLn(matches.Items[i].AsString);
// Single result
found := doc.QuerySingle('$.products[0].name', singleResult);
if found then
WriteLn('First product: ' + singleResult.AsString)
else
WriteLn('No products found');
end;