A complete Go implementation of JSONPath that fully complies with RFC 9535. Provides both a command-line tool and a Go library with support for all standard JSONPath features.
- Complete RFC 9535 Implementation
- Root node access (
$
) - Child node access (
.key
or['key']
) - Recursive descent (
..
) - Array indices (
[0]
,[-1]
) - Array slices (
[start:end:step]
) - Array wildcards (
[*]
) - Multiple indices (
[1,2,3]
) - Filter expressions (
[?(@.price < 10)]
)
- Root node access (
- Command Line Tool (
jp
)- Beautiful colorized output
- Syntax highlighting for JSON
- File and stdin input support
- Formatted and compact output options
- User-friendly error messages
- UTF-8 support with proper CJK display
- Go Library
- Clean API design
- Type-safe operations
- Rich examples
- Comprehensive documentation
- Complete rewrite with RFC 9535 compliance
- Full implementation of JSONPath specification (RFC 9535)
- Enhanced error handling and reporting
- Improved performance and reliability
- Better support for various JSONPath expressions
- Breaking Changes
- API changes to align with RFC specification
- Updated function signatures for better usability
- Modified error types for more detailed error reporting
- Documentation
- Updated documentation to reflect RFC compliance
- Added more examples and use cases
- Improved API documentation
- Centralize version management
- Add version.go for centralized version control
- Update cmd/jp to use centralized version
- Fix UTF-8 encoding in Chinese comments
- Enhanced filter expressions
- Full support for logical operators (
&&
,||
,!
) - Proper handling of complex filter conditions
- Support for De Morgan's Law in negated expressions
- Improved numeric and string comparisons
- Better error messages
- Full support for logical operators (
- Improved API design
- New simplified
Query
function for easier usage - Deprecated
Compile/Execute
in favor ofQuery
- Better error handling and reporting
- New simplified
- Updated examples
- New examples demonstrating logical operators
- Updated code to use new
Query
function - Fixed UTF-8 encoding issues in examples
- Enhanced filter expressions
- Full support for logical operators (
&&
,||
,!
) - Proper handling of complex filter conditions
- Support for De Morgan's Law in negated expressions
- Improved numeric and string comparisons
- Better error messages
- Full support for logical operators (
- Enhanced colorized output
- Beautiful syntax highlighting for JSON
- Colorful command-line interface
- Improved readability for nested structures
- Better UTF-8 support
- Fixed CJK character display
- Proper handling of multi-byte characters
- Initial stable release
- Basic JSONPath query support
- Command-line tool implementation
- Core filter expression support
- Basic colorized output
- Initial documentation
- Basic error handling
- UTF-8 support
# Add tap
brew tap davidhoo/tap
# Install jsonpath
brew install jsonpath
go install github.com/davidhoo/jsonpath/cmd/jp@latest
Download the appropriate binary for your platform from the releases page.
jp [-p <jsonpath_expression>] [-f <json_file>] [-c]
Options:
-p
JSONPath expression (if not specified, output entire JSON)-f
JSON file path (reads from stdin if not specified)-c
Compact output (no formatting)--no-color
Disable colored output-h
Show help information-v
Show version information
# Output entire JSON with syntax highlighting
jp -f data.json
# Query specific path
jp -f data.json -p '$.store.book[*].author'
# Filter with conditions
jp -f data.json -p '$.store.book[?(@.price > 10)]'
# Read from stdin
echo '{"name": "John"}' | jp -p '$.name'
# Compact output
jp -f data.json -c
import "github.com/davidhoo/jsonpath"
// Query JSON data
result, err := jsonpath.Query(data, "$.store.book[*].author")
if err != nil {
log.Fatal(err)
}
// Handle result
authors, ok := result.([]interface{})
if !ok {
log.Fatal("unexpected result type")
}
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/davidhoo/jsonpath"
)
func main() {
// JSON data
data := `{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
}
]
}
}`
// Parse JSON
var v interface{}
if err := json.Unmarshal([]byte(data), &v); err != nil {
log.Fatal(err)
}
// Execute JSONPath query
result, err := jsonpath.Query(v, "$.store.book[?(@.price < 10)].title")
if err != nil {
log.Fatal(err)
}
// Print result
fmt.Printf("%v\n", result) // ["Sayings of the Century"]
}
// Get all prices (recursive)
"$..price"
// Get books with specific price range
"$.store.book[?(@.price < 10)].title"
// Get all authors
"$.store.book[*].author"
// Get first book
"$.store.book[0]"
// Get last book
"$.store.book[-1]"
// Get first two books
"$.store.book[0:2]"
// Get books with price > 10 and category == fiction
"$.store.book[?(@.price > 10 && @.category == 'fiction')]"
// Get all non-reference books
"$.store.book[?(!(@.category == 'reference'))]"
// Get books with price > 10 or author containing 'Evelyn'
"$.store.book[?(@.price > 10 || @.author == 'Evelyn Waugh')]"
// Get length of book array
"$.store.book.length()"
// Get all keys of store object
"$.store.keys()"
// Get all values of store object
"$.store.values()"
// Get minimum book price
"$.store.book[*].price.min()"
// Get maximum book price
"$.store.book[*].price.max()"
// Get average book price
"$.store.book[*].price.avg()"
// Get total price of all books
"$.store.book[*].price.sum()"
// Count fiction books
"$.store.book[*].category.count('fiction')"
// Match book titles with regex pattern
"$.store.book[?@.title.match('^S.*')]"
// Search for books with titles starting with 'S'
"$.store.book[*].title.search('^S.*')"
// Chain multiple functions
"$.store.book[?@.price > 10].title.length()"
// Complex function chaining
"$.store.book[?@.price > $.store.book[*].price.avg()].title"
// Combine search and filter
"$.store.book[?@.title.match('^S.*') && @.price < 10].author"
Handle results according to their type using type assertions:
// Single value result
if str, ok := result.(string); ok {
// Handle string result
}
// Array result
if arr, ok := result.([]interface{}); ok {
for _, item := range arr {
// Handle each item
}
}
// Object result
if obj, ok := result.(map[string]interface{}); ok {
// Handle object
}
-
RFC 9535 Compliance
- Support for all standard operators
- Standard-compliant syntax parsing
- Standard result formatting
-
Filter Support
- Comparison operators:
<
,>
,<=
,>=
,==
,!=
- Logical operators:
&&
,||
,!
- Support for complex filter conditions
- Support for numeric and string comparisons
- Proper handling of negated expressions using De Morgan's Law
- Nested filter conditions with parentheses
- Comparison operators:
-
Result Handling
- Array operations return array results
- Single value access returns original type
- Type-safe result handling
-
Error Handling
- Detailed error messages
- Syntax error reporting
- Runtime error handling
Issues and Pull Requests are welcome!
MIT License
# Run all tests
go test -v ./...
# Run tests with coverage
go test -v -coverprofile=coverage.out ./...
# View coverage report in browser
go tool cover -html=coverage.out
# Run benchmarks
go test -bench=. -benchmem ./...
The test suite is organized into several categories:
- Unit Tests: Testing individual functions and components
- Integration Tests: Testing interactions between components
- Benchmark Tests: Performance testing of critical paths
- Example Tests: Documenting usage through examples
Current test coverage metrics:
- Overall Coverage: 90%+
- Core Package: 90%+
- Command Line Tool: 90%+
When contributing tests, please follow these guidelines:
- Use table-driven tests for similar test cases
- Provide clear test case descriptions
- Test both success and error cases
- Include edge cases and boundary conditions
- Use test suites for related functionality
- Add benchmarks for performance-critical code
Example of a well-structured test:
func TestParseJSONPath(t *testing.T) {
tests := []struct {
name string
input string
expected interface{}
wantErr bool
}{
{
name: "simple path",
input: "$.store.book",
expected: []string{"store", "book"},
wantErr: false,
},
// Add more test cases...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseJSONPath(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseJSONPath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("ParseJSONPath() = %v, want %v", result, tt.expected)
}
})
}
}