use super::filter::{lookup, scalar_text, FilterExpr};
use serde_json::{Map, Value};
pub fn filter(items: Vec<Value>, filters: &[FilterExpr], command: Option<&str>) -> Vec<Value> {
if filters.is_empty() {
return items;
}
items
.into_iter()
.filter(|item| super::filter::matches_all(filters, item, command))
.collect()
}
pub fn sort(mut items: Vec<Value>, key: &str, command: Option<&str>) -> Vec<Value> {
let path: Vec<String> = key.split('.').map(str::to_string).collect();
items.sort_by(|a, b| {
let left = lookup(a, &path, command);
let right = lookup(b, &path, command);
match (left, right) {
(None, None) => std::cmp::Ordering::Equal,
(None, Some(_)) => std::cmp::Ordering::Greater,
(Some(_), None) => std::cmp::Ordering::Less,
(Some(l), Some(r)) => compare(l, r),
}
});
items
}
fn compare(left: &Value, right: &Value) -> std::cmp::Ordering {
if let (Some(l), Some(r)) = (left.as_f64(), right.as_f64()) {
return l.partial_cmp(&r).unwrap_or(std::cmp::Ordering::Equal);
}
match (scalar_text(left), scalar_text(right)) {
(Some(l), Some(r)) => l.cmp(&r),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
}
pub fn dedupe(items: Vec<Value>, key: &str, command: Option<&str>) -> Vec<Value> {
let path: Vec<String> = key.split('.').map(str::to_string).collect();
let mut seen = std::collections::HashSet::new();
let mut out = Vec::with_capacity(items.len());
for item in items {
match lookup(&item, &path, command).and_then(scalar_text) {
Some(text) => {
if seen.insert(text) {
out.push(item);
}
}
None => out.push(item),
}
}
out
}
pub fn limit(mut items: Vec<Value>, max: usize) -> Vec<Value> {
if max > 0 && items.len() > max {
items.truncate(max);
}
items
}
pub fn project(items: Vec<Value>, keys: &[String], command: Option<&str>) -> Vec<Value> {
if keys.is_empty() {
return items;
}
let paths = compile_paths(keys);
items
.into_iter()
.map(|item| project_with(item, keys, &paths, command))
.collect()
}
pub(super) fn compile_paths(keys: &[String]) -> Vec<Vec<String>> {
keys.iter()
.map(|key| key.split('.').map(str::to_string).collect())
.collect()
}
pub(super) fn project_with(
item: Value,
keys: &[String],
paths: &[Vec<String>],
command: Option<&str>,
) -> Value {
let Value::Object(_) = &item else {
return item;
};
let mut out = Map::new();
for (key, path) in keys.iter().zip(paths) {
if let Some(found) = lookup(&item, path, command) {
out.insert(key.clone(), found.clone());
}
}
Value::Object(out)
}
pub fn project_one(item: Value, keys: &[String], command: Option<&str>) -> Value {
if keys.is_empty() {
return item;
}
project_with(item, keys, &compile_paths(keys), command)
}
pub fn truncate_strings(value: &mut Value, max: usize) -> bool {
if max == 0 {
return false;
}
match value {
Value::String(s) => {
if s.chars().count() > max {
let cut = s
.char_indices()
.nth(max)
.map_or(s.len(), |(byte_idx, _)| byte_idx);
s.truncate(cut);
true
} else {
false
}
}
Value::Array(items) => {
let mut hit = false;
for item in items {
hit |= truncate_strings(item, max);
}
hit
}
Value::Object(map) => {
let mut hit = false;
for (_, item) in map.iter_mut() {
hit |= truncate_strings(item, max);
}
hit
}
Value::Null | Value::Bool(_) | Value::Number(_) => false,
}
}