Skip to content

Repository files navigation

Cobblestone

A better path to data. Powerful data querying and transformation library for Elixir

Continuous IntegrationModule VersionHex DocsTotal DownloadLicenseLast Updated

Cobblestone provides a path-based query language for navigating and filtering nested maps and lists in Elixir, inspired by jq, JSONPath, and XPath. It offers a simple yet powerful syntax for extracting, transforming, and manipulating data structures.

Features

  • Path Navigation: Direct access (.store.book) and recursive search (..author)
  • Array Operations: Indexing ([0], [-1]), slicing ([1:3]), multiple indices ([0,2,4])
  • Filtering: Existence filters ([isbn]) and comparison filters ([price>20])
  • Data Transformation: map() for transforming arrays, select() for filtering
  • Pipeline Operations: Chain operations with the pipe operator (|)
  • Object/Array Construction: Build new structures with {key: .path} and [.path1, .path2]
  • Elixir Integration: Supports both atom and string keys, pipeline-friendly APIs
  • Error Handling: Structured error responses with helpful messages

Installation

Add cobblestone to your list of dependencies in mix.exs:

defdepsdo[{:cobblestone,"~> 0.1.0"}]end

Quick Start

# Basic path navigationdata=%{"user"=>%{"name"=>"Alice","age"=>30}}Cobblestone.get_at_path!(data,".user.name")# => "Alice"# Array operationsbooks=%{"items"=>[%{"title"=>"Elixir in Action","price"=>35}]}Cobblestone.get_at_path!(books,".items[0].title")# => ["Elixir in Action"]# Pipeline operationsusers=[%{"name"=>"Alice","active"=>true,"age"=>30},%{"name"=>"Bob","active"=>false,"age"=>25}]Cobblestone.get_at_path!(users,".[] | select(.active) | map(.name)")# => ["Alice"]# Object constructionuser=%{"first"=>"Alice","last"=>"Smith","role"=>"admin"}Cobblestone.get_at_path!(user,"{name: .first, position: .role}")# => %{"name" => "Alice", "position" => "admin"}

Path Syntax Guide

Basic Navigation

SyntaxDescriptionExample
.Identity (returns input). => entire structure
.keyAccess map key.user.name
..keyRecursive search..author finds all authors
.key1.key2Nested access.store.book

Array Operations

SyntaxDescriptionExample
[n]Index access[0] first, [-1] last
[n:m]Slice range[1:3] elements 1-2
[:m]Slice from start[:3] first 3 elements
[n:]Slice to end[2:] from index 2 onward
[n,m,o]Multiple indices[0,2,4] specific elements
[]Array/object iterator.items[] all array elements

Filtering

SyntaxDescriptionExample
[key]Has key[isbn] items with isbn field
[key>value]Greater than[price>20]
[key<value]Less than[price<10]
[key>=value]Greater or equal[age>=18]
[key<=value]Less or equal[stock<=5]
[key==value]Equals[status=="active"]

Functions

FunctionDescriptionExample
select(expr)Filter elementsselect(.active)
map(expr)Transform elementsmap(.title)

Construction

SyntaxDescriptionExample
{key: expr}Object construction{name: .first, age: .age}
[expr, expr]Array construction[.name, .email, .phone]

Pipeline

SyntaxDescriptionExample
expr | exprChain operations.users | select(.active) | map(.name)

API Reference

Core Functions

get_at_path(data, path)

Query data using path expressions, returning {:ok, result} or {:error, details}.

data=%{"users"=>[%{"name"=>"Alice"},%{"name"=>"Bob"}]}{:ok,names}=Cobblestone.get_at_path(data,".users[].name")# => {:ok, ["Alice", "Bob"]}

get_at_path!(data, path)

Like get_at_path/2 but returns the result directly or raises on error.

Cobblestone.get_at_path!(data,".users[].name")# => ["Alice", "Bob"]

Functional API

at(path)

Creates a reusable query function for the given path expression.

get_names=Cobblestone.at(".users[].name"){:ok,names}=get_names.(data)

at!(path)

Creates a reusable query function that raises on error.

get_names=Cobblestone.at!(".users[].name")names=get_names.(data)# Raises on error

Extraction Functions

extract(data, path_map)

Extract multiple values from data using a map of path expressions.

paths=%{user_name: ".user.name",user_age: ".user.age",settings: ".user.settings"}{:ok,extracted}=Cobblestone.extract(data,paths)

extract!(data, path_map)

Like extract/2 but raises on error.

extracted=Cobblestone.extract!(data,paths)

Advanced Examples

Complex Data Extraction

api_response=%{"data"=>%{"users"=>[%{"id"=>1,"name"=>"Alice","posts"=>[%{"title"=>"Hello"}]},%{"id"=>2,"name"=>"Bob","posts"=>[%{"title"=>"World"}]}]},"meta"=>%{"total"=>2}}# Extract multiple fieldsCobblestone.extract!(api_response,%{names: ".data.users[].name",total: ".meta.total",all_posts: ".data.users[].posts[].title"})# => %{names: ["Alice", "Bob"], total: 2, all_posts: ["Hello", "World"]}

Data Transformation Pipeline

products=[%{"name"=>"Laptop","price"=>999,"stock"=>5,"active"=>true},%{"name"=>"Mouse","price"=>25,"stock"=>0,"active"=>true},%{"name"=>"Keyboard","price"=>75,"stock"=>10,"active"=>false}]# Complex pipeline: active products in stock, sorted by priceproducts|>Cobblestone.get_at_path!(".[] | select(.active)")|>Cobblestone.get_at_path!(".[stock>0]")|>Cobblestone.get_at_path!("{name: .name, price: .price}")# => %{"name" => "Laptop", "price" => 999}

Working with Atom Keys

# Cobblestone seamlessly handles atom keysconfig=%{database: %{host: "localhost",port: 5432,credentials: %{username: "admin",password: "secret"}}}Cobblestone.get_at_path!(config,".database.credentials.username")# => "admin"

Comparison with Similar Tools

Sample data structure used for comparison:

%{"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}}}

Query Syntax Comparison

Use CaseXPathJSONPathjqCobblestone
All book authors/store/book/author$.store.book[*].author.store.book[].author.store.book[].author
All authors (recursive)//author$..author.. | .author? // empty..author
All store items/store/*$.store.*.store[].store[]
All prices in store/store//price$.store..price.store | .. | .price? // empty.store..price
Third book//book[3]$..book[2].store.book[2]..book[2]
Last book//book[last()]$..book[-1:].store.book[-1]..book[-1]
First two books//book[position()<3]$..book[:2].store.book[:2]..book[:2]
Books with ISBN//book[isbn]$..book[?(@.isbn)].store.book[] | select(.isbn)..book[isbn]
Books under $10//book[price<10]$..book[?(@.price<10)].store.book[] | select(.price < 10)..book[price<10]
Books over $20//book[price>20]$..book[?(@.price>20)].store.book[] | select(.price > 20)..book[price>20]
All elements//*$..*...
Chain filtersN/A$.store.book[?(@.isbn)][?(@.price<10)].store.book[] | select(.isbn) | select(.price < 10).store.book | [isbn] | [price<10]
Extract titlesN/A$.store.book[*].title.store.book[].title.store.book | map(.title)
Filter by fieldN/A$.store.book[?(@.isbn)].store.book[] | select(.isbn).store.book[] | select(.isbn)
Transform to objectN/AN/A{title: .title, cost: .price}{title: .title, cost: .price}
Array constructionN/AN/A[.name, .age, .email][.name, .age, .email]

Key Differences

Cobblestone vs JSONPath:

  • Cleaner, more intuitive syntax
  • Native Elixir integration with atoms and strings
  • Built-in pipeline operations
  • Object/array construction support

Cobblestone vs XPath:

  • JSON/Map-oriented rather than XML-focused
  • Simpler array operations
  • Modern functional operations (map, select)

Cobblestone vs jq:

  • Native Elixir library (no external dependencies)
  • Seamless integration with Elixir pipelines
  • Simplified syntax for common operations
  • Type-safe with Elixir's pattern matching

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Inspired by jq, JSONPath, and XPath
  • Built with Erlang's leex and yecc parser generators
  • Designed for the Elixir community

About

A better path to data. Powerful data querying and transformation library for Elixir

Topics

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages