refactor; add order() fn; add examples
This commit is contained in:
parent
d1419a2198
commit
6db5f79743
11 changed files with 388 additions and 11 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -19,7 +19,7 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
|||
|
||||
[[package]]
|
||||
name = "jsonfilter"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "jsonfilter"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
32
README.md
32
README.md
|
@ -1,2 +1,32 @@
|
|||
# JSONFilter
|
||||
JSONFilter is a rust crate letting you filter JSON objects based on another json object as a filter. Think of MongoDBs `find()` function but as a filter.
|
||||
`jsonfilter` is a Rust crate designed to facilitate filtering and comparing JSON values based on specified criteria. It provides functions for comparing JSON values, applying filters to JSON objects, and determining if a filter matches a given JSON object. Think of MongoDBs `find()` function but as a filter function.
|
||||
|
||||
## Usage
|
||||
To use `jsonfilter`, add it to your `Cargo.toml` and add the following to your Rust code:
|
||||
```rust
|
||||
use jsonfilter::{order, matches};
|
||||
```
|
||||
|
||||
### Comparing JSON Values
|
||||
You can compare two JSON values using the `order` function:
|
||||
```rust
|
||||
use serde_json::json;
|
||||
use std::cmp::Ordering;
|
||||
use jsonfilter::order;
|
||||
|
||||
let a = json!(10);
|
||||
let b = json!(5);
|
||||
assert_eq!(order(&a, &b), Ordering::Greater);
|
||||
```
|
||||
|
||||
### Matching Filters
|
||||
To check if a JSON object matches a filter, use the `matches` function:
|
||||
```rust
|
||||
use serde_json::json;
|
||||
use jsonfilter::matches;
|
||||
|
||||
let filter = json!({"name": "John", "age": 30});
|
||||
let obj = json!({"name": "John", "age": 30, "city": "New York"});
|
||||
|
||||
assert!(matches(&filter, &obj));
|
||||
```
|
||||
|
|
38
examples/array.rs
Normal file
38
examples/array.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use jsonfilter::{try_matches, FilterError};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn main() {
|
||||
let filter = json!({"$or": [{"tags": {"$in": "programming"}},{"tags": {"$in": "rust"}}]});
|
||||
|
||||
let obj1 = json!({"name": "John Doe", "tags": ["rust", "programming", "development"]});
|
||||
let obj2 = json!({"name": "Alice Smith", "tags": ["web", "development", "javascript"]});
|
||||
let obj3 = json!({"name": "Bob Brown", "tags": ["python", "programming", "machine learning"]});
|
||||
|
||||
println!("Filter:");
|
||||
println!("{}", filter);
|
||||
println!("Objects:");
|
||||
println!("Object 1: {}", obj1);
|
||||
println!("Object 2: {}", obj2);
|
||||
println!("Object 3: {}", obj3);
|
||||
|
||||
match_objects(&filter, &obj1);
|
||||
match_objects(&filter, &obj2);
|
||||
match_objects(&filter, &obj3);
|
||||
}
|
||||
|
||||
fn match_objects(filter: &Value, obj: &Value) {
|
||||
match try_matches(filter, obj) {
|
||||
Ok(result) => {
|
||||
if result {
|
||||
println!("Filter matches the object");
|
||||
} else {
|
||||
println!("Filter does not match the object");
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
FilterError::InvalidFilter => println!("Invalid filter"),
|
||||
FilterError::UnknownOperator => println!("Unknown operator in filter"),
|
||||
FilterError::KeyNotFound => println!("Key not found in object"),
|
||||
},
|
||||
}
|
||||
}
|
16
examples/comparison.rs
Normal file
16
examples/comparison.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use jsonfilter::order;
|
||||
use serde_json::json;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
fn main() {
|
||||
let a = json!(10);
|
||||
let b = json!(5);
|
||||
println!("Comparing JSON values:");
|
||||
println!("a: {}", a);
|
||||
println!("b: {}", b);
|
||||
match order(&a, &b) {
|
||||
Ordering::Greater => println!("a is greater than b"),
|
||||
Ordering::Less => println!("a is less than b"),
|
||||
Ordering::Equal => println!("a is equal to b"),
|
||||
}
|
||||
}
|
28
examples/error_handling.rs
Normal file
28
examples/error_handling.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use jsonfilter::{try_matches, FilterError};
|
||||
use serde_json::json;
|
||||
|
||||
fn main() {
|
||||
let filter = json!({"name": "John", "other": "key"});
|
||||
let obj = json!({"name": "John", "age": 30});
|
||||
|
||||
println!("Applying filter:");
|
||||
println!("{}", filter);
|
||||
println!("To object:");
|
||||
println!("{}", obj);
|
||||
|
||||
match try_matches(&filter, &obj) {
|
||||
Ok(result) => {
|
||||
if result {
|
||||
println!("Filter matches the object");
|
||||
} else {
|
||||
println!("Filter does not match the object");
|
||||
}
|
||||
}
|
||||
// FilterError::KeyNotFound mostly means the object did not pass the filter, but it's still an error though
|
||||
Err(err) => match err {
|
||||
FilterError::InvalidFilter => println!("Invalid filter"),
|
||||
FilterError::UnknownOperator => println!("Unknown operator in filter"),
|
||||
FilterError::KeyNotFound => println!("Key not found in object"),
|
||||
},
|
||||
}
|
||||
}
|
17
examples/filter.rs
Normal file
17
examples/filter.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use jsonfilter::matches;
|
||||
use serde_json::json;
|
||||
|
||||
fn main() {
|
||||
let filter = json!({"name": "John", "age": 30});
|
||||
let obj = json!({"name": "John", "age": 30, "city": "New York"});
|
||||
|
||||
println!("Applying filter:");
|
||||
println!("{}", filter);
|
||||
println!("To object:");
|
||||
println!("{}", obj);
|
||||
if matches(&filter, &obj) {
|
||||
println!("Filter matches the object");
|
||||
} else {
|
||||
println!("Filter does not match the object");
|
||||
}
|
||||
}
|
53
examples/nested_filters.rs
Normal file
53
examples/nested_filters.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use jsonfilter::{try_matches, FilterError};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn main() {
|
||||
// Filter can be as complex as you want
|
||||
let filter = json!({
|
||||
"$and": [
|
||||
{"$or": [
|
||||
{"age": {"$gte": 18}},
|
||||
{"is_student": true}
|
||||
]},
|
||||
{"$not": {
|
||||
"$or": [
|
||||
{"city": "New York"},
|
||||
{"city": "Los Angeles"}
|
||||
]
|
||||
}}
|
||||
]
|
||||
});
|
||||
|
||||
let obj1 = json!({"age": 25, "is_student": false, "city": "Chicago"});
|
||||
let obj2 = json!({"age": 16, "is_student": true, "city": "Miami"});
|
||||
let obj3 = json!({"age": 30, "is_student": false, "city": "New York"});
|
||||
|
||||
println!("Filter:");
|
||||
println!("{}", filter);
|
||||
println!("Objects:");
|
||||
println!("Object 1: {}", obj1);
|
||||
println!("Object 2: {}", obj2);
|
||||
println!("Object 3: {}", obj3);
|
||||
|
||||
// Matching objects against the filter
|
||||
match_objects(&filter, &obj1);
|
||||
match_objects(&filter, &obj2);
|
||||
match_objects(&filter, &obj3);
|
||||
}
|
||||
|
||||
fn match_objects(filter: &Value, obj: &Value) {
|
||||
match try_matches(filter, obj) {
|
||||
Ok(result) => {
|
||||
if result {
|
||||
println!("Filter matches the object");
|
||||
} else {
|
||||
println!("Filter does not match the object");
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
FilterError::InvalidFilter => println!("Invalid filter"),
|
||||
FilterError::UnknownOperator => println!("Unknown operator in filter"),
|
||||
FilterError::KeyNotFound => println!("Key not found in object"),
|
||||
},
|
||||
}
|
||||
}
|
38
examples/range.rs
Normal file
38
examples/range.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use jsonfilter::{try_matches, FilterError};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn main() {
|
||||
let filter = json!({"$and": [{"age": {"$gte": 18}}, {"age": {"$lte": 30}}]});
|
||||
|
||||
let obj1 = json!({"name": "John Doe", "age": 25});
|
||||
let obj2 = json!({"name": "Alice Smith", "age": 35});
|
||||
let obj3 = json!({"name": "Bob Brown", "age": 20});
|
||||
|
||||
println!("Filter:");
|
||||
println!("{}", filter);
|
||||
println!("Objects:");
|
||||
println!("Object 1: {}", obj1);
|
||||
println!("Object 2: {}", obj2);
|
||||
println!("Object 3: {}", obj3);
|
||||
|
||||
match_objects(&filter, &obj1);
|
||||
match_objects(&filter, &obj2);
|
||||
match_objects(&filter, &obj3);
|
||||
}
|
||||
|
||||
fn match_objects(filter: &Value, obj: &Value) {
|
||||
match try_matches(filter, obj) {
|
||||
Ok(result) => {
|
||||
if result {
|
||||
println!("Filter matches the object");
|
||||
} else {
|
||||
println!("Filter does not match the object");
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
FilterError::InvalidFilter => println!("Invalid filter"),
|
||||
FilterError::UnknownOperator => println!("Unknown operator in filter"),
|
||||
FilterError::KeyNotFound => println!("Key not found in object"),
|
||||
},
|
||||
}
|
||||
}
|
39
examples/regex.rs
Normal file
39
examples/regex.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use jsonfilter::{try_matches, FilterError};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
fn main() {
|
||||
// You can filter text with regex
|
||||
let filter = json!({"description": {"$regex": "(Rust|Python)"}});
|
||||
|
||||
let obj1 = json!({"name": "John Doe", "description": "Enthusiastic about programming in Rust"});
|
||||
let obj2 = json!({"name": "Alice Smith", "description": "Experienced in web development"});
|
||||
let obj3 = json!({"name": "Bob Brown", "description": "Loves to code in Python"});
|
||||
|
||||
println!("Filter:");
|
||||
println!("{}", filter);
|
||||
println!("Objects:");
|
||||
println!("Object 1: {}", obj1);
|
||||
println!("Object 2: {}", obj2);
|
||||
println!("Object 3: {}", obj3);
|
||||
|
||||
match_objects(&filter, &obj1);
|
||||
match_objects(&filter, &obj2);
|
||||
match_objects(&filter, &obj3);
|
||||
}
|
||||
|
||||
fn match_objects(filter: &Value, obj: &Value) {
|
||||
match try_matches(filter, obj) {
|
||||
Ok(result) => {
|
||||
if result {
|
||||
println!("Filter matches the object");
|
||||
} else {
|
||||
println!("Filter does not match the object");
|
||||
}
|
||||
}
|
||||
Err(err) => match err {
|
||||
FilterError::InvalidFilter => println!("Invalid filter"),
|
||||
FilterError::UnknownOperator => println!("Unknown operator in filter"),
|
||||
FilterError::KeyNotFound => println!("Key not found in object"),
|
||||
},
|
||||
}
|
||||
}
|
134
src/lib.rs
134
src/lib.rs
|
@ -1,6 +1,73 @@
|
|||
mod test;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
/// Compares two JSON values `a` and `b` based on their ordering.
|
||||
///
|
||||
/// This function evaluates the relationship between the JSON values `a` and `b` using the following criteria:
|
||||
/// - If `a` is greater than `b`, returns `std::cmp::Ordering::Greater`.
|
||||
/// - If `a` is less than `b`, returns `std::cmp::Ordering::Less`.
|
||||
/// - If `a` is equal to `b`, returns `std::cmp::Ordering::Equal`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `a` - A reference to a `serde_json::Value` representing the first JSON value.
|
||||
/// * `b` - A reference to a `serde_json::Value` representing the second JSON value.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `std::cmp::Ordering` indicating the relationship between `a` and `b`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the values cant be compared.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use serde_json::json;
|
||||
/// use std::cmp::Ordering;
|
||||
/// use jsonfilter::order;
|
||||
///
|
||||
/// let a = json!(10);
|
||||
/// let b = json!(5);
|
||||
/// assert_eq!(order(&a, &b), Ordering::Greater);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn order(a: &serde_json::Value, b: &serde_json::Value) -> std::cmp::Ordering {
|
||||
if matches(
|
||||
&serde_json::json!({
|
||||
"a": { "$gt": b}
|
||||
}),
|
||||
&serde_json::json!({
|
||||
"a": a
|
||||
}),
|
||||
) {
|
||||
Ordering::Greater
|
||||
} else if matches(
|
||||
&serde_json::json!({
|
||||
"a": { "$lt": b}
|
||||
}),
|
||||
&serde_json::json!({
|
||||
"a": a
|
||||
}),
|
||||
) {
|
||||
Ordering::Less
|
||||
} else if matches(
|
||||
&serde_json::json!({
|
||||
"a": b
|
||||
}),
|
||||
&serde_json::json!({
|
||||
"a": a
|
||||
}),
|
||||
) {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn less_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
|
||||
if a.is_f64() {
|
||||
let a = a.as_f64().unwrap();
|
||||
|
@ -14,8 +81,12 @@ fn less_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
|
|||
let a = a.as_u64().unwrap();
|
||||
let b = b.as_u64().unwrap();
|
||||
a < b
|
||||
} else if a.is_string() {
|
||||
let a = a.as_str().unwrap();
|
||||
let b = b.as_str().unwrap();
|
||||
a < b
|
||||
} else {
|
||||
false
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,8 +103,12 @@ fn greater_than_num(a: &serde_json::Value, b: &serde_json::Value) -> bool {
|
|||
let a = a.as_u64().unwrap();
|
||||
let b = b.as_u64().unwrap();
|
||||
a > b
|
||||
} else if a.is_string() {
|
||||
let a = a.as_str().unwrap();
|
||||
let b = b.as_str().unwrap();
|
||||
a > b
|
||||
} else {
|
||||
false
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,21 +164,34 @@ pub fn try_matches(
|
|||
match op.as_str() {
|
||||
"$and" => {
|
||||
if let serde_json::Value::Array(and_list) = op_arg {
|
||||
let and_list_bool: Vec<bool> = and_list
|
||||
let and_list_bool: Vec<Result<bool, FilterError>> = and_list
|
||||
.iter()
|
||||
.map(|sub_filter| matches(sub_filter, obj))
|
||||
.map(|sub_filter| try_matches(sub_filter, obj))
|
||||
.collect();
|
||||
return Ok(!and_list_bool.iter().any(|x| !x));
|
||||
if let Some(err) = and_list_bool.iter().find(|x| x.is_err()) {
|
||||
return *err;
|
||||
}
|
||||
return Ok(!and_list_bool.iter().map(|x| x.unwrap()).any(|x| !x));
|
||||
}
|
||||
return Err(FilterError::InvalidFilter);
|
||||
}
|
||||
"$or" => {
|
||||
if let serde_json::Value::Array(or_list) = op_arg {
|
||||
let or_list_bool: Vec<bool> = or_list
|
||||
let or_list_bool: Vec<Result<bool, FilterError>> = or_list
|
||||
.iter()
|
||||
.map(|sub_filter| matches(sub_filter, obj))
|
||||
.map(|sub_filter| try_matches(sub_filter, obj))
|
||||
.collect();
|
||||
return Ok(or_list_bool.iter().any(|x| *x));
|
||||
if let Some(err) = or_list_bool.iter().find(|x| x.is_err()) {
|
||||
return *err;
|
||||
}
|
||||
return Ok(or_list_bool.iter().map(|x| x.unwrap()).any(|x| x));
|
||||
}
|
||||
return Err(FilterError::InvalidFilter);
|
||||
}
|
||||
"$not" => {
|
||||
if let Some(inner) = filter.get("$not") {
|
||||
let new_filter = inner;
|
||||
return Ok(!try_matches(new_filter, obj)?);
|
||||
}
|
||||
return Err(FilterError::InvalidFilter);
|
||||
}
|
||||
|
@ -145,6 +233,10 @@ pub fn try_matches(
|
|||
}
|
||||
}
|
||||
|
||||
check(&conditions)
|
||||
}
|
||||
|
||||
fn check(conditions: &[Result<bool, FilterError>]) -> Result<bool, FilterError> {
|
||||
conditions.iter().find(|x| x.is_err()).map_or_else(
|
||||
|| Ok(!conditions.iter().map(|x| x.unwrap()).any(|x| !x)),
|
||||
|possible_error| *possible_error,
|
||||
|
@ -163,6 +255,32 @@ fn match_operator(
|
|||
let op = keys.first().unwrap().as_str();
|
||||
let op_arg = val.get(op).unwrap();
|
||||
match op {
|
||||
"$and" => {
|
||||
if let serde_json::Value::Array(and_list) = op_arg {
|
||||
let and_list_bool: Vec<Result<bool, FilterError>> = and_list
|
||||
.iter()
|
||||
.map(|sub_filter| try_matches(sub_filter, raw_obj))
|
||||
.collect();
|
||||
if let Some(err) = and_list_bool.iter().find(|x| x.is_err()) {
|
||||
return *err;
|
||||
}
|
||||
return Ok(!and_list_bool.iter().map(|x| x.unwrap()).any(|x| !x));
|
||||
}
|
||||
return Err(FilterError::InvalidFilter);
|
||||
}
|
||||
"$or" => {
|
||||
if let serde_json::Value::Array(or_list) = op_arg {
|
||||
let or_list_bool: Vec<Result<bool, FilterError>> = or_list
|
||||
.iter()
|
||||
.map(|sub_filter| try_matches(sub_filter, raw_obj))
|
||||
.collect();
|
||||
if let Some(err) = or_list_bool.iter().find(|x| x.is_err()) {
|
||||
return *err;
|
||||
}
|
||||
return Ok(or_list_bool.iter().map(|x| x.unwrap()).any(|x| x));
|
||||
}
|
||||
return Err(FilterError::InvalidFilter);
|
||||
}
|
||||
"$lt" => {
|
||||
if let Some(a) = obj.get(key) {
|
||||
return Ok(less_than_num(a, op_arg));
|
||||
|
|
Loading…
Add table
Reference in a new issue