use crate::path::{Accessor, Selector, Step};
use crate::value::{Map, Value};
const RESERVED_KEYS: [&str; 3] = ["__proto__", "constructor", "prototype"];
fn reserved(key: &str) -> bool {
RESERVED_KEYS.contains(&key)
}
fn index(obj: &Value, step: &Step) -> Value {
match (obj, step) {
(Value::Object(map), Step::Key(key)) => map.get(key).cloned().unwrap_or(Value::Undefined),
(Value::Array(items), Step::Index(i)) => items.get(*i).cloned().unwrap_or(Value::Undefined),
(Value::String(s), Step::Index(i)) => s
.chars()
.nth(*i)
.map_or(Value::Undefined, |c| Value::String(c.to_string())),
_ => Value::Undefined,
}
}
fn truthy(value: &Value) -> bool {
match value {
Value::Undefined | Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => *n != 0.0 && !n.is_nan(),
Value::String(s) => !s.is_empty(),
Value::Array(_) | Value::Object(_) => true,
}
}
fn child(obj: &Value, step: &Step) -> Value {
if obj.is_undefined() {
Value::Bool(false)
} else {
index(obj, step)
}
}
fn accessor_to_step(accessor: &Accessor) -> Step {
match accessor {
Accessor::Key(k) => Step::Key(k.clone()),
Accessor::Index(i) => Step::Index(*i),
Accessor::Predicate(_) => unreachable!("predicate resolved before step"),
}
}
fn get_paths(obj: &Value, path: &[Accessor]) -> Option<Vec<Vec<Step>>> {
let (accessor, rest) = path.split_first()?;
if let Accessor::Predicate(pred) = accessor {
let mut results = Vec::new();
match obj {
Value::Array(items) => {
for (i, item) in items.iter().enumerate() {
if pred(item, &Selector::Index(i)) {
results.extend(resolve_literal(obj, Step::Index(i), rest));
}
}
}
Value::Object(map) => {
for (key, val) in map.iter() {
if pred(val, &Selector::Key(key.clone())) {
results.extend(resolve_literal(obj, Step::Key(key.clone()), rest));
}
}
}
_ => {}
}
return Some(results);
}
Some(resolve_literal(obj, accessor_to_step(accessor), rest))
}
fn resolve_literal(obj: &Value, step: Step, rest: &[Accessor]) -> Vec<Vec<Step>> {
let next_obj = child(obj, &step);
match get_paths(&next_obj, rest) {
Some(paths) => paths
.into_iter()
.map(|mut p| {
p.insert(0, step.clone());
p
})
.collect(),
None => vec![vec![step]],
}
}
fn get_value(obj: &Value, path: &[Step]) -> Value {
let mut current = obj.clone();
for step in path {
if !truthy(¤t) {
return current;
}
current = index(¤t, step);
}
current
}
fn step_is_reserved(step: &Step) -> bool {
matches!(step, Step::Key(k) if reserved(k))
}
fn coerce(obj: &Value, step: &Step) -> Value {
match step {
Step::Key(_) => match obj {
Value::Object(_) => obj.clone(),
_ => Value::object(Map::new()),
},
Step::Index(_) => match obj {
Value::Array(_) => obj.clone(),
_ => Value::array(Vec::new()),
},
}
}
fn set_value(obj: &Value, path: &[Step], target: &Value) -> Value {
let Some((step, rest)) = path.split_first() else {
return target.clone();
};
let value = child(obj, step);
if step_is_reserved(step) {
return obj.clone();
}
let next_obj = coerce(obj, step);
let next_value = set_value(&value, rest, target);
match step {
Step::Index(i) => {
if next_value.is_undefined() {
if obj.is_undefined() {
return obj.clone();
}
let items = next_obj.as_array().expect("coerced to array");
if *i < items.len() {
let mut cloned = items.to_vec();
cloned.remove(*i);
return Value::array(cloned);
}
next_obj
} else if next_value.same_value(&value) {
obj.clone()
} else {
let items = next_obj.as_array().expect("coerced to array");
let mut cloned = items.to_vec();
if *i >= cloned.len() {
cloned.resize(*i + 1, Value::Undefined);
}
cloned[*i] = next_value;
Value::array(cloned)
}
}
Step::Key(key) => {
if next_value.is_undefined() {
if obj.is_undefined() {
return obj.clone();
}
let map = next_obj.as_object().expect("coerced to map");
if map.contains_key(key) {
let mut cloned = map.clone();
cloned.remove(key);
return Value::object(cloned);
}
next_obj
} else if next_value.same_value(&value) {
obj.clone()
} else {
let map = next_obj.as_object().expect("coerced to map");
let mut cloned = map.clone();
cloned.insert(key.clone(), next_value);
Value::object(cloned)
}
}
}
}
pub fn update_in(obj: Value, path: &[Accessor], updater: Option<&dyn Fn(Value) -> Value>) -> Value {
let Some(paths) = get_paths(&obj, path) else {
return obj;
};
if paths.iter().any(|p| p.iter().any(step_is_reserved)) {
return obj;
}
let mut current = obj;
for p in paths {
let target = match updater {
Some(f) => f(get_value(¤t, &p)),
None => Value::Undefined,
};
current = set_value(¤t, &p, &target);
}
current
}