Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# AGENTS.md - JSONPath Project

This file provides guidance to AI agents working on the JSONPath project.

## Project Overview

JSONPath is a Go library for querying JSON data using JSONPath expressions. It provides:

- **Language**: Go (Golang) 1.25+
- **Type**: Library/package
- **Purpose**: JSON data querying and manipulation
- **Features**: JSONPath expression parsing and evaluation

## Key Configuration Files

- `go.mod` - Go module definition
- `jsonpath.go` - Main library implementation
- `jsonpath_test.go` - Comprehensive test suite
- `.travis.yml` - CI/CD configuration

## Build and Test Commands

### Installation
```bash
# Install the package
go get github.com/your-repo/jsonpath

# Install dependencies
go mod tidy
```

### Development
```bash
# Build the project
go build

# Run the library
go run .
```

### Testing
```bash
# Run all tests
go test -v

# Run tests with coverage
go test -cover

# Run specific test functions
go test -run TestFunctionName
```

### Code Quality
```bash
# Format code
gofmt -w .

# Check for formatting issues
gofmt -d .
```

## Project Structure

```
jsonpath.go # Main library implementation
jsonpath_test.go # Test suite
go.mod # Go module definition
readme.md # Project documentation
```

## Code Style Guidelines

- **Go Standards**: Follow official Go code review comments
- **Formatting**: Use `gofmt` for consistent formatting
- **Naming**: Use camelCase for variables, PascalCase for exported types
- **Error Handling**: Explicit error handling (no panic for expected errors)
- **Documentation**: Add godoc comments for exported functions/types
- **Testing**: Comprehensive test coverage for all functionality

## Testing Instructions

- **Test Files**: `jsonpath_test.go` contains comprehensive tests
- **Test Patterns**: Table-driven tests for JSONPath expressions
- **Coverage**: Aim for high test coverage of all code paths
- **Edge Cases**: Test malformed JSON, invalid paths, boundary conditions

## JSONPath Implementation Details

- **Expression Parsing**: Custom JSONPath parser implementation
- **Query Evaluation**: Efficient JSON traversal algorithms
- **Result Handling**: Support for various return types
- **Error Handling**: Clear error messages for invalid expressions

## Security Considerations

- **Input Validation**: Validate JSONPath expressions
- **Memory Safety**: Handle large JSON documents efficiently
- **Error Messages**: Don't expose sensitive information
- **Dependency Management**: Minimal dependencies for security

## Performance Considerations

- **Parsing Optimization**: Efficient JSONPath expression parsing
- **Traversal Algorithms**: Optimized JSON document traversal
- **Memory Usage**: Minimize allocations during query execution
- **Benchmarking**: Consider adding performance benchmarks

## Usage Examples

```go
// Basic usage example
result, err := jsonpath.Get(pathExpression, jsonData)
if err != nil {
// Handle error
}
// Use result...
```

## Git Conventions

- **Commit Messages**: Clear, descriptive commit messages
- **Branching**: Use feature branches for new development
- **Pull Requests**: Required for merging to main branch
- **Tags**: Use semantic versioning for releases

## CI/CD

- **Travis CI**: Configured in `.travis.yml`
- **Automated Testing**: Runs on every push/PR
- **Build Verification**: Ensures project builds successfully
- **Test Coverage**: Reports test coverage metrics

## Documentation

- **readme.md**: Contains usage examples and API documentation
- **Godoc**: Use godoc comments for inline documentation
- **Examples**: Include practical usage examples

## Dependency Management

- **Minimal Dependencies**: Only standard library dependencies
- **Go Modules**: Uses Go modules for dependency management
- **Updates**: Regularly update Go version in `go.mod`

## Future Enhancements

- **Additional Features**: Consider adding more JSONPath features
- **Performance**: Optimize for large JSON documents
- **Compatibility**: Ensure compatibility with JSONPath standards
- **Documentation**: Expand usage examples and tutorials

## Task Implementation
1. **Analyze Requirements**: Refer to `README.md` for detailed feature specifications and system design.
2. **Implementation**: Modify source code in the respective directories (e.g., `src/`, `internal/`).
3. **Verification**: Run provided build and test commands (see above) to ensure correctness.
4. **Push Changes**:
- Commit changes: `git commit -m "feat: implement <feature>"`
- Push to remote: `git push origin <branch-name>`
63 changes: 59 additions & 4 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (c *Compiled) String() string {

func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
var err error
for _, s := range c.steps {
for i, s := range c.steps {
// "key", "idx"
switch s.op {
case "key":
Expand Down Expand Up @@ -132,6 +132,24 @@ func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
if err != nil {
return nil, err
}
case "recursive":
obj = getAllDescendants(obj)
// Heuristic: if next step is key, exclude slices from candidates to avoid double-matching
// (once as container via implicit map, once as individual elements)
if i+1 < len(c.steps) && c.steps[i+1].op == "key" {
if candidates, ok := obj.([]interface{}); ok {
filtered := []interface{}{}
for _, cand := range candidates {
// Filter out Slices (but keep Maps and others)
// because get_key on Slice iterates children, which are already in candidates
v := reflect.ValueOf(cand)
if v.Kind() != reflect.Slice {
filtered = append(filtered, cand)
}
}
obj = filtered
}
}
default:
return nil, fmt.Errorf("expression don't support in filter")
}
Expand Down Expand Up @@ -161,8 +179,8 @@ func tokenize(query string) ([]string, error) {
if token == "." {
continue
} else if token == ".." {
if tokens[len(tokens)-1] != "*" {
tokens = append(tokens, "*")
if len(tokens) == 0 || tokens[len(tokens)-1] != ".." {
tokens = append(tokens, "..")
}
token = "."
continue
Expand Down Expand Up @@ -215,7 +233,7 @@ func tokenize(query string) ([]string, error) {
}

/*
op: "root", "key", "idx", "range", "filter", "scan"
op: "root", "key", "idx", "range", "filter", "scan"
*/
func parse_token(token string) (op string, key string, args interface{}, err error) {
if token == "$" {
Expand All @@ -224,6 +242,9 @@ func parse_token(token string) (op string, key string, args interface{}, err err
if token == "*" {
return "scan", "*", nil, nil
}
if token == ".." {
return "recursive", "..", nil, nil
}

bracket_idx := strings.Index(token, "[")
if bracket_idx < 0 {
Expand Down Expand Up @@ -720,3 +741,37 @@ func cmp_any(obj1, obj2 interface{}, op string) (bool, error) {

return false, nil
}

func getAllDescendants(obj interface{}) []interface{} {
res := []interface{}{}
var recurse func(curr interface{})
recurse = func(curr interface{}) {
res = append(res, curr)
v := reflect.ValueOf(curr)
if !v.IsValid() {
return
}

kind := v.Kind()
if kind == reflect.Ptr {
v = v.Elem()
if !v.IsValid() {
return
}
kind = v.Kind()
}

switch kind {
case reflect.Map:
for _, k := range v.MapKeys() {
recurse(v.MapIndex(k).Interface())
}
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
recurse(v.Index(i).Interface())
}
}
}
recurse(obj)
return res
}
85 changes: 84 additions & 1 deletion jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 || res_v[2].(float64) != 8.99 || res_v[3].(float64) != 22.99 {
t.Errorf("exp: [8.95, 12.99, 8.99, 22.99], got: %v", res)
}

// range
res, err = JsonPathLookup(json_data, "$.store.book[0:1].price")
t.Log(err, res)
Expand Down Expand Up @@ -214,6 +214,15 @@ var token_cases = []map[string]interface{}{
"query": "$....author",
"tokens": []string{"$", "*", "author"},
},
// New test cases for recursive descent
map[string]interface{}{
"query": "$..author",
"tokens": []string{"$", "..", "author"},
},
map[string]interface{}{
"query": "$....author",
"tokens": []string{"$", "..", "author"},
},
}

func Test_jsonpath_tokenize(t *testing.T) {
Expand Down Expand Up @@ -1243,3 +1252,77 @@ func Test_jsonpath_rootnode_is_nested_array_range(t *testing.T) {
// t.Fatal("idx: 0, should be 3.1, got: %v", ares[1])
//}
}

func TestRecursiveDescent(t *testing.T) {
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
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
`
var json_data interface{}
json.Unmarshal([]byte(data), &json_data)

// Test case: $..author should return all authors
authors_query := "$..author"
res, err := JsonPathLookup(json_data, authors_query)
if err != nil {
t.Fatalf("Failed to execute recursive query %s: %v", authors_query, err)
}

authors, ok := res.([]interface{})
if !ok {
t.Fatalf("Expected []interface{}, got %T", res)
}

if len(authors) != 4 {
t.Errorf("Expected 4 authors, got %d: %v", len(authors), authors)
}

// Test case: $..price should return all prices (5 total: 4 books + 1 bicycle)
price_query := "$..price"
res, err = JsonPathLookup(json_data, price_query)
if err != nil {
t.Fatalf("Failed to execute recursive query %s: %v", price_query, err)
}
prices, ok := res.([]interface{})
if !ok {
t.Fatalf("Expected []interface{}, got %T", res)
}
if len(prices) != 5 {
t.Errorf("Expected 5 prices, got %d: %v", len(prices), prices)
}
}