README.md

# JsonPath for Elixir

A **minimal JSONPath engine in Elixir** using **LEEX + YECC** generated modules.

This library supports querying nested maps and lists using JSONPath syntax.
It currently implements a **partial subset of [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html)** and passes **400/702 compliance tests**.

---

## Features

- Child selector (`.`) and descendant selector (`..`)
- Wildcards (`*`) for maps and arrays
- Array slices and indices (`[0:2]`, `[-1]`)
- Unions (`[0,2]`)
- Basic filters (`[?@.price<20]`)
- Returns **paths and values** in evaluation
- Convenience `query/2` helper for automatic tokenize → parse → evaluate
- Works with **Elixir maps and lists**

> ⚠️ Partial implementation: not all RFC 9535 features are implemented yet.

---

## Installation

Add to your `mix.exs`:

```elixir
def deps do
  [
    {:json_path, "~> 0.1.0"}
  ]
end
```

Then fetch dependencies:

```bash
mix deps.get
```

---

## Usage

### Basic Queries

```elixir
data = %{
  "store" => %{
    "book" => [
      %{"title" => "Elixir in Action", "price" => 15},
      %{"title" => "Programming Phoenix", "price" => 20}
    ],
    "bicycle" => %{"color" => "red", "price" => 100}
  }
}

# Get all book prices
JsonPath.query(data, "$.store.book[*].price")
# => [{"$['store']['book'][0]['price']", 15}, {"$['store']['book'][1]['price']", 20}]

# Get bicycle color
JsonPath.query(data, "$.store.bicycle.color")
# => [{"$['store']['bicycle']['color']", "red"}]
```

---

### Advanced Queries

```elixir
# Descendant selector
JsonPath.query(data, "$..price")
# => [{"$['store']['book'][0]['price']", 15}, {"$['store']['book'][1]['price']", 20}, {"$['store']['bicycle']['price']", 100}]

# Union selection
JsonPath.query(data, "$.store.book[0,1]")
# => [{"$['store']['book'][0]", ...}, {"$['store']['book'][1]", ...}]

# Array slice
JsonPath.query(data, "$.store.book[0:2]")
# => first two books

# Negative index
JsonPath.query(data, "$.store.book[-1]")
# => last book

# Filter with comparison
JsonPath.query(data, "$..book[?@.price<20]")
# => books with price < 20

# Logical filter (AND/OR)
JsonPath.query(data, "$..book[?@.price>10 && @.price<30]")
# => books with 10 < price < 30
```

---

## Functions

### `JsonPath.query(data, path_string)`
Convenience function to **tokenize → parse → evaluate** in one call.

- **Parameters:**
  - `data` – Map or List to query
  - `path_string` – JSONPath string
- **Returns:**
  - List of `{path_string, value}` or `{:error, reason}`

### `JsonPath.tokenize(query)`
Tokenizes a JSONPath string using the lexer.
- **Returns:** `{:ok, tokens, line}` or `{:error, reason}`

### `JsonPath.parse(tokens)`
Parses a token list into an AST.
- **Returns:** `{:ok, ast}` or `{:error, reason}`

### `JsonPath.evaluate(ast, data)`
Evaluates a parsed AST against data.
- **Returns:** List of `{path_string, value}`

---

## Compliance & Coverage

- Partial **RFC 9535** implementation
- **Passing 400/702 JSONPath compliance tests** ([link](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/tree/main))
- Work in progress for full RFC coverage

---

## Contributing

1. Fork the repository
2. Implement or improve features
3. Run tests: `mix test`
4. Submit a pull request

---

## License

Apache License 2.0 © Jacob Aronoff