use crate::value::Value;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq)]
pub enum Json {
Null,
Bool(bool),
Int(i64),
Real(f64, Option<String>),
Str(String),
Array(Vec<Json>),
Object(Vec<(String, Json)>),
}
impl Json {
pub fn type_name(&self) -> &'static str {
match self {
Json::Null => "null",
Json::Bool(true) => "true",
Json::Bool(false) => "false",
Json::Int(_) => "integer",
Json::Real(..) => "real",
Json::Str(_) => "text",
Json::Array(_) => "array",
Json::Object(_) => "object",
}
}
pub fn to_sql(&self) -> Value {
match self {
Json::Null => Value::Null,
Json::Bool(b) => Value::Integer(*b as i64),
Json::Int(i) => Value::Integer(*i),
Json::Real(r, _) => Value::Real(*r),
Json::Str(s) => Value::Text(s.clone()),
Json::Array(_) | Json::Object(_) => Value::Text(self.serialize()),
}
}
pub fn serialize(&self) -> String {
let mut s = String::new();
self.write(&mut s);
s
}
pub fn quote(&self) -> String {
if let Json::Real(r, _) = self {
if r.is_infinite() {
return String::from(if *r < 0.0 { "-9.0e+999" } else { "9.0e+999" });
}
}
self.serialize()
}
pub fn to_jsonb(&self) -> Vec<u8> {
let mut out = Vec::new();
self.write_jsonb(&mut out);
out
}
fn write_jsonb(&self, out: &mut Vec<u8>) {
match self {
Json::Null => push_jsonb(out, JSONB_NULL, &[]),
Json::Bool(true) => push_jsonb(out, JSONB_TRUE, &[]),
Json::Bool(false) => push_jsonb(out, JSONB_FALSE, &[]),
Json::Int(i) => push_jsonb(out, JSONB_INT, i.to_string().as_bytes()),
Json::Real(r, src) => {
let s = match src {
Some(t) => t.clone(),
None if r.is_infinite() => {
String::from(if *r < 0.0 { "-9e999" } else { "9e999" })
}
None => crate::exec::eval::format_real(*r),
};
push_jsonb(out, JSONB_FLOAT, s.as_bytes());
}
Json::Str(s) => {
if json_needs_escape(s) {
push_jsonb(out, JSONB_TEXTJ, json_escape_body(s).as_bytes());
} else {
push_jsonb(out, JSONB_TEXT, s.as_bytes());
}
}
Json::Array(items) => {
let mut body = Vec::new();
for it in items {
it.write_jsonb(&mut body);
}
push_jsonb(out, JSONB_ARRAY, &body);
}
Json::Object(members) => {
let mut body = Vec::new();
for (k, v) in members {
if json_needs_escape(k) {
push_jsonb(&mut body, JSONB_TEXTJ, json_escape_body(k).as_bytes());
} else {
push_jsonb(&mut body, JSONB_TEXT, k.as_bytes());
}
v.write_jsonb(&mut body);
}
push_jsonb(out, JSONB_OBJECT, &body);
}
}
}
pub fn from_jsonb(bytes: &[u8]) -> Option<Json> {
let (j, rest) = decode_jsonb(bytes)?;
rest.is_empty().then_some(j)
}
pub fn pretty(&self, indent: &str) -> String {
let mut s = String::new();
self.write_pretty(&mut s, indent, 0);
s
}
fn write_pretty(&self, out: &mut String, indent: &str, depth: usize) {
let pad = |out: &mut String, n: usize| {
for _ in 0..n {
out.push_str(indent);
}
};
match self {
Json::Array(items) if !items.is_empty() => {
out.push('[');
for (i, it) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push('\n');
pad(out, depth + 1);
it.write_pretty(out, indent, depth + 1);
}
out.push('\n');
pad(out, depth);
out.push(']');
}
Json::Object(members) if !members.is_empty() => {
out.push('{');
for (i, (k, v)) in members.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push('\n');
pad(out, depth + 1);
write_json_string(k, out);
out.push_str(": ");
v.write_pretty(out, indent, depth + 1);
}
out.push('\n');
pad(out, depth);
out.push('}');
}
_ => self.write(out),
}
}
fn write(&self, out: &mut String) {
match self {
Json::Null => out.push_str("null"),
Json::Bool(true) => out.push_str("true"),
Json::Bool(false) => out.push_str("false"),
Json::Int(i) => out.push_str(&i.to_string()),
Json::Real(r, _) if r.is_infinite() => {
out.push_str(if *r < 0.0 { "-9e999" } else { "9e999" });
}
Json::Real(_, Some(src)) => out.push_str(src),
Json::Real(r, None) => out.push_str(&crate::exec::eval::format_real(*r)),
Json::Str(s) => write_json_string(s, out),
Json::Array(items) => {
out.push('[');
for (i, it) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
it.write(out);
}
out.push(']');
}
Json::Object(members) => {
out.push('{');
for (i, (k, v)) in members.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_json_string(k, out);
out.push(':');
v.write(out);
}
out.push('}');
}
}
}
}
const JSONB_NULL: u8 = 0;
const JSONB_TRUE: u8 = 1;
const JSONB_FALSE: u8 = 2;
const JSONB_INT: u8 = 3;
const JSONB_INT5: u8 = 4;
const JSONB_FLOAT: u8 = 5;
const JSONB_FLOAT5: u8 = 6;
const JSONB_TEXT: u8 = 7;
const JSONB_TEXTJ: u8 = 8;
const JSONB_TEXT5: u8 = 9;
const JSONB_TEXTRAW: u8 = 10;
const JSONB_ARRAY: u8 = 11;
const JSONB_OBJECT: u8 = 12;
fn push_jsonb(out: &mut Vec<u8>, ty: u8, payload: &[u8]) {
let n = payload.len();
if n <= 11 {
out.push(((n as u8) << 4) | ty);
} else if n <= 0xFF {
out.push((12 << 4) | ty);
out.push(n as u8);
} else if n <= 0xFFFF {
out.push((13 << 4) | ty);
out.extend_from_slice(&(n as u16).to_be_bytes());
} else if n <= 0xFFFF_FFFF {
out.push((14 << 4) | ty);
out.extend_from_slice(&(n as u32).to_be_bytes());
} else {
out.push((15 << 4) | ty);
out.extend_from_slice(&(n as u64).to_be_bytes());
}
out.extend_from_slice(payload);
}
fn decode_jsonb(b: &[u8]) -> Option<(Json, &[u8])> {
let header = *b.first()?;
let ty = header & 0x0F;
let mut rest = &b[1..];
let n = match header >> 4 {
d @ 0..=11 => d as usize,
12 => {
let v = *rest.first()? as usize;
rest = &rest[1..];
v
}
13 => {
let v = u16::from_be_bytes([*rest.first()?, *rest.get(1)?]) as usize;
rest = &rest[2..];
v
}
14 => {
let mut a = [0u8; 4];
a.copy_from_slice(rest.get(..4)?);
rest = &rest[4..];
u32::from_be_bytes(a) as usize
}
_ => {
let mut a = [0u8; 8];
a.copy_from_slice(rest.get(..8)?);
rest = &rest[8..];
u64::from_be_bytes(a) as usize
}
};
let payload = rest.get(..n)?;
let after = &rest[n..];
let text = || core::str::from_utf8(payload).ok().map(String::from);
let j = match ty {
JSONB_NULL => Json::Null,
JSONB_TRUE => Json::Bool(true),
JSONB_FALSE => Json::Bool(false),
JSONB_INT | JSONB_INT5 | JSONB_FLOAT | JSONB_FLOAT5 => {
let t = core::str::from_utf8(payload).ok()?;
match parse(t) {
Some(j @ (Json::Int(_) | Json::Real(..))) => j,
_ => return None,
}
}
JSONB_TEXT | JSONB_TEXTRAW => Json::Str(text()?),
JSONB_TEXTJ | JSONB_TEXT5 => {
let mut quoted = String::with_capacity(n + 2);
quoted.push('"');
quoted.push_str(core::str::from_utf8(payload).ok()?);
quoted.push('"');
match parse("ed)? {
Json::Str(s) => Json::Str(s),
_ => return None,
}
}
JSONB_ARRAY => {
let mut items = Vec::new();
let mut p = payload;
while !p.is_empty() {
let (it, next) = decode_jsonb(p)?;
items.push(it);
p = next;
}
Json::Array(items)
}
JSONB_OBJECT => {
let mut members = Vec::new();
let mut p = payload;
while !p.is_empty() {
let (label, next) = decode_jsonb(p)?;
let (val, next2) = decode_jsonb(next)?;
let key = match label {
Json::Str(s) => s,
_ => return None,
};
members.push((key, val));
p = next2;
}
Json::Object(members)
}
_ => return None,
};
Some((j, after))
}
fn json_needs_escape(s: &str) -> bool {
s.chars()
.any(|c| matches!(c, '"' | '\\') || (c as u32) < 0x20)
}
fn json_escape_body(s: &str) -> String {
let mut q = String::new();
write_json_string(s, &mut q);
q[1..q.len() - 1].to_string()
}
fn write_json_string(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => {
out.push_str("\\u");
for shift in [12, 8, 4, 0] {
let nib = (c as u32 >> shift) & 0xf;
out.push(char::from_digit(nib, 16).unwrap());
}
}
c => out.push(c),
}
}
out.push('"');
}
pub fn parse(text: &str) -> Option<Json> {
parse_with_error_position(text).ok()
}
pub fn parse_with_error_position(text: &str) -> Result<Json, usize> {
let mut p = Parser {
bytes: text.as_bytes(),
pos: 0,
};
p.skip_ws();
if p.peek().is_none() {
return Err(0);
}
let v = p.value()?;
p.skip_ws();
if p.pos == p.bytes.len() {
Ok(v)
} else {
Err(0)
}
}
pub fn is_strict_json(text: &str) -> bool {
let mut p = StrictParser {
b: text.as_bytes(),
i: 0,
};
p.ws();
p.value() && {
p.ws();
p.i == p.b.len()
}
}
struct StrictParser<'a> {
b: &'a [u8],
i: usize,
}
impl StrictParser<'_> {
fn ws(&mut self) {
while matches!(self.b.get(self.i), Some(b' ' | b'\t' | b'\n' | b'\r')) {
self.i += 1;
}
}
fn lit(&mut self, w: &str) -> bool {
if self.b[self.i..].starts_with(w.as_bytes()) {
self.i += w.len();
true
} else {
false
}
}
fn value(&mut self) -> bool {
self.ws();
match self.b.get(self.i) {
Some(b'{') => self.object(),
Some(b'[') => self.array(),
Some(b'"') => self.string(),
Some(b't') => self.lit("true"),
Some(b'f') => self.lit("false"),
Some(b'n') => self.lit("null"),
Some(b'-' | b'0'..=b'9') => self.number(),
_ => false,
}
}
fn object(&mut self) -> bool {
self.i += 1; self.ws();
if self.b.get(self.i) == Some(&b'}') {
self.i += 1;
return true;
}
loop {
self.ws();
if self.b.get(self.i) != Some(&b'"') || !self.string() {
return false;
}
self.ws();
if self.b.get(self.i) != Some(&b':') {
return false;
}
self.i += 1;
if !self.value() {
return false;
}
self.ws();
match self.b.get(self.i) {
Some(b',') => self.i += 1, Some(b'}') => {
self.i += 1;
return true;
}
_ => return false,
}
}
}
fn array(&mut self) -> bool {
self.i += 1; self.ws();
if self.b.get(self.i) == Some(&b']') {
self.i += 1;
return true;
}
loop {
if !self.value() {
return false;
}
self.ws();
match self.b.get(self.i) {
Some(b',') => self.i += 1, Some(b']') => {
self.i += 1;
return true;
}
_ => return false,
}
}
}
fn string(&mut self) -> bool {
self.i += 1; loop {
match self.b.get(self.i) {
None => return false,
Some(b'"') => {
self.i += 1;
return true;
}
Some(b'\\') => {
self.i += 1;
match self.b.get(self.i) {
Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => self.i += 1,
Some(b'u') => {
self.i += 1;
for _ in 0..4 {
match self.b.get(self.i) {
Some(c) if c.is_ascii_hexdigit() => self.i += 1,
_ => return false,
}
}
}
_ => return false,
}
}
Some(&c) if c < 0x20 => return false,
Some(_) => self.i += 1,
}
}
}
fn number(&mut self) -> bool {
let start = self.i;
if self.b.get(self.i) == Some(&b'-') {
self.i += 1;
}
match self.b.get(self.i) {
Some(b'0') => self.i += 1, Some(b'1'..=b'9') => {
while matches!(self.b.get(self.i), Some(b'0'..=b'9')) {
self.i += 1;
}
}
_ => return false,
}
if self.b.get(self.i) == Some(&b'.') {
self.i += 1;
if !matches!(self.b.get(self.i), Some(b'0'..=b'9')) {
return false;
}
while matches!(self.b.get(self.i), Some(b'0'..=b'9')) {
self.i += 1;
}
}
if matches!(self.b.get(self.i), Some(b'e' | b'E')) {
self.i += 1;
if matches!(self.b.get(self.i), Some(b'+' | b'-')) {
self.i += 1;
}
if !matches!(self.b.get(self.i), Some(b'0'..=b'9')) {
return false;
}
while matches!(self.b.get(self.i), Some(b'0'..=b'9')) {
self.i += 1;
}
}
self.i > start
}
}
struct Parser<'a> {
bytes: &'a [u8],
pos: usize,
}
impl Parser<'_> {
fn peek(&self) -> Option<u8> {
self.bytes.get(self.pos).copied()
}
fn err<T>(&self) -> Result<T, usize> {
Err(self.pos)
}
fn skip_ws(&mut self) {
loop {
match self.peek() {
Some(b' ' | b'\t' | b'\n' | b'\r') => self.pos += 1,
Some(b'/') => match self.bytes.get(self.pos + 1) {
Some(b'/') => {
self.pos += 2;
while let Some(c) = self.peek() {
self.pos += 1;
if c == b'\n' {
break;
}
}
}
Some(b'*') => {
self.pos += 2;
while self.pos < self.bytes.len() {
if self.bytes[self.pos] == b'*'
&& self.bytes.get(self.pos + 1) == Some(&b'/')
{
self.pos += 2;
break;
}
self.pos += 1;
}
}
_ => break,
},
_ => break,
}
}
}
fn value(&mut self) -> Result<Json, usize> {
self.skip_ws();
match self.peek() {
Some(b'{') => self.object(),
Some(b'[') => self.array(),
Some(b'"') => self.string(b'"').map(Json::Str),
Some(b'\'') => self.string(b'\'').map(Json::Str),
Some(b't') => self.literal("true", Json::Bool(true)),
Some(b'f') => self.literal("false", Json::Bool(false)),
Some(b'n') => self.literal("null", Json::Null),
Some(b'I') => self.literal("Infinity", Json::Real(f64::INFINITY, None)),
Some(b'N') => self.literal("NaN", Json::Null),
Some(b'-' | b'+' | b'.' | b'0'..=b'9') => self.number(),
_ => self.err(),
}
}
fn literal(&mut self, word: &str, val: Json) -> Result<Json, usize> {
if self.bytes[self.pos..].starts_with(word.as_bytes()) {
self.pos += word.len();
Ok(val)
} else {
self.err()
}
}
fn object(&mut self) -> Result<Json, usize> {
self.pos += 1; let mut members = Vec::new();
loop {
self.skip_ws();
if self.peek() == Some(b'}') {
self.pos += 1;
return Ok(Json::Object(members));
}
let key = self.object_key()?;
self.skip_ws();
if self.peek() != Some(b':') {
return self.err();
}
self.pos += 1;
let val = self.value()?;
members.push((key, val));
self.skip_ws();
match self.peek() {
Some(b',') => self.pos += 1,
Some(b'}') => {
self.pos += 1;
return Ok(Json::Object(members));
}
_ => return self.err(),
}
}
}
fn object_key(&mut self) -> Result<String, usize> {
match self.peek() {
Some(b'"') => self.string(b'"'),
Some(b'\'') => self.string(b'\''),
Some(c) if c.is_ascii_alphabetic() || c == b'_' || c == b'$' => {
let start = self.pos;
while let Some(c) = self.peek() {
if c.is_ascii_alphanumeric() || c == b'_' || c == b'$' {
self.pos += 1;
} else {
break;
}
}
Ok(core::str::from_utf8(&self.bytes[start..self.pos])
.unwrap_or("")
.to_string())
}
_ => {
while let Some(c) = self.peek() {
if c.is_ascii_alphanumeric() || c == b'_' || c == b'$' {
self.pos += 1;
} else {
break;
}
}
self.err()
}
}
}
fn array(&mut self) -> Result<Json, usize> {
self.pos += 1; let mut items = Vec::new();
loop {
self.skip_ws();
if self.peek() == Some(b']') {
self.pos += 1;
return Ok(Json::Array(items));
}
let val = self.value()?;
items.push(val);
self.skip_ws();
match self.peek() {
Some(b',') => self.pos += 1,
Some(b']') => {
self.pos += 1;
return Ok(Json::Array(items));
}
_ => return self.err(),
}
}
}
fn string(&mut self, quote: u8) -> Result<String, usize> {
self.pos += 1; let mut s = String::new();
loop {
let Some(c) = self.peek() else {
return self.err();
};
if c == quote {
self.pos += 1;
return Ok(s);
}
self.pos += 1;
match c {
b'\\' => {
let esc_pos = self.pos;
let Some(esc) = self.peek() else {
return self.err();
};
self.pos += 1;
match esc {
b'"' => s.push('"'),
b'\'' => s.push('\''),
b'\\' => s.push('\\'),
b'/' => s.push('/'),
b'n' => s.push('\n'),
b't' | b'v' => s.push('\t'),
b'r' => s.push('\r'),
b'b' => s.push('\u{08}'),
b'f' => s.push('\u{0c}'),
b'0' => s.push('\0'),
b'\n' => {}
b'\r' => {
if self.peek() == Some(b'\n') {
self.pos += 1;
}
}
b'x' => {
let hi = self.hex_digit()?;
let lo = self.hex_digit()?;
s.push((hi * 16 + lo) as u8 as char);
}
b'u' => {
let cp = self.hex4()?;
if (0xD800..=0xDBFF).contains(&cp) {
if self.peek() == Some(b'\\') {
self.pos += 1;
if self.peek() == Some(b'u') {
self.pos += 1;
let lo = self.hex4()?;
let c = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
match char::from_u32(c) {
Some(ch) => s.push(ch),
None => return self.err(),
}
} else {
return self.err();
}
} else {
return self.err();
}
} else {
match char::from_u32(cp) {
Some(ch) => s.push(ch),
None => return self.err(),
}
}
}
_ => return Err(esc_pos),
}
}
0x80..=0xFF => {
let start = self.pos - 1;
while self.peek().is_some_and(|b| b >= 0x80) {
self.pos += 1;
}
match core::str::from_utf8(&self.bytes[start..self.pos]) {
Ok(chunk) => s.push_str(chunk),
Err(_) => return self.err(),
}
}
c => s.push(c as char),
}
}
}
fn hex4(&mut self) -> Result<u32, usize> {
let mut v = 0u32;
for _ in 0..4 {
v = v * 16 + self.hex_digit()?;
}
Ok(v)
}
fn hex_digit(&mut self) -> Result<u32, usize> {
let Some(c) = self.peek() else {
return self.err();
};
let Some(d) = (c as char).to_digit(16) else {
return self.err();
};
self.pos += 1;
Ok(d)
}
fn number(&mut self) -> Result<Json, usize> {
let start = self.pos;
let neg = match self.peek() {
Some(b'-') => {
self.pos += 1;
true
}
Some(b'+') => {
self.pos += 1;
false
}
_ => false,
};
match self.peek() {
Some(b'I') => {
return self.literal(
"Infinity",
Json::Real(
if neg {
f64::NEG_INFINITY
} else {
f64::INFINITY
},
None,
),
);
}
Some(b'N') => return self.literal("NaN", Json::Null),
_ => {}
}
if self.peek() == Some(b'0') && matches!(self.bytes.get(self.pos + 1), Some(b'x' | b'X')) {
self.pos += 2;
let digits_start = self.pos;
let mut v: u64 = 0;
while let Some(c) = self.peek() {
let Some(d) = (c as char).to_digit(16) else {
break;
};
v = v.wrapping_mul(16).wrapping_add(d as u64);
self.pos += 1;
}
if self.pos == digits_start {
return Err(start); }
let i = v as i64;
return Ok(Json::Int(if neg { i.wrapping_neg() } else { i }));
}
let mut is_real = false;
while let Some(c) = self.peek() {
match c {
b'0'..=b'9' => self.pos += 1,
b'.' | b'e' | b'E' | b'+' | b'-' => {
is_real = true;
self.pos += 1;
}
_ => break,
}
}
if self.pos == start || (neg && self.pos == start + 1) {
return Err(start); }
let Ok(tok) = core::str::from_utf8(&self.bytes[start..self.pos]) else {
return Err(start);
};
if !is_real {
if let Ok(i) = tok.parse::<i64>() {
return Ok(Json::Int(i));
}
}
match parse_json5_real(tok) {
Some(r) => Ok(Json::Real(
r,
is_strict_json_number(tok).then(|| String::from(tok)),
)),
None => Err(start),
}
}
}
fn is_strict_json_number(t: &str) -> bool {
let b = t.as_bytes();
let mut i = 0;
if b.get(i) == Some(&b'-') {
i += 1;
}
match b.get(i) {
Some(b'0') => i += 1, Some(c) if c.is_ascii_digit() => {
while b.get(i).is_some_and(u8::is_ascii_digit) {
i += 1;
}
}
_ => return false, }
if b.get(i) == Some(&b'.') {
i += 1;
if !b.get(i).is_some_and(u8::is_ascii_digit) {
return false; }
while b.get(i).is_some_and(u8::is_ascii_digit) {
i += 1;
}
}
if matches!(b.get(i), Some(b'e' | b'E')) {
i += 1;
if matches!(b.get(i), Some(b'+' | b'-')) {
i += 1;
}
if !b.get(i).is_some_and(u8::is_ascii_digit) {
return false;
}
while b.get(i).is_some_and(u8::is_ascii_digit) {
i += 1;
}
}
i == b.len()
}
fn parse_json5_real(tok: &str) -> Option<f64> {
let tok = tok.strip_prefix('+').unwrap_or(tok);
tok.parse::<f64>().ok()
}
pub fn navigate<'a>(root: &'a Json, path: &str) -> Option<&'a Json> {
let segs = parse_path(path)?;
let mut cur = root;
for seg in &segs {
match (cur, seg) {
(Json::Object(members), Seg::Key(k)) => {
cur = members.iter().find(|(kk, _)| kk == k).map(|(_, v)| v)?;
}
(Json::Array(items), Seg::Index(idx)) => {
let n = idx.resolve_read(items.len())?;
cur = items.get(n)?;
}
_ => return None,
}
}
Some(cur)
}
pub fn value_to_json(v: &Value) -> Json {
match v {
Value::Null => Json::Null,
Value::Integer(i) => Json::Int(*i),
Value::Real(r) => Json::Real(*r, None),
Value::Text(s) => Json::Str(s.clone()),
Value::Blob(_) => Json::Str(String::new()),
}
}
pub fn arrow(doc: &Value, path_arg: &Value, as_text: bool) -> Value {
if matches!(doc, Value::Null) {
return Value::Null;
}
let text = crate::exec::eval::to_text(doc);
let Some(root) = parse(&text) else {
return Value::Null;
};
let path = arrow_path(path_arg);
match navigate(&root, &path) {
None => Value::Null,
Some(node) => {
if as_text {
node.to_sql()
} else {
Value::Text(node.serialize())
}
}
}
}
fn arrow_path(v: &Value) -> String {
match v {
Value::Integer(i) if *i < 0 => alloc::format!("$[#-{}]", i.unsigned_abs()),
Value::Integer(i) => alloc::format!("$[{i}]"),
Value::Text(s) if s.starts_with('$') => s.clone(),
Value::Text(s) => alloc::format!("$.{s}"),
other => alloc::format!("$.{}", crate::exec::eval::to_text(other)),
}
}
#[derive(Clone, Copy)]
enum Idx {
Abs(usize),
FromEnd(usize),
}
impl Idx {
fn resolve_read(self, len: usize) -> Option<usize> {
match self {
Idx::Abs(n) => Some(n),
Idx::FromEnd(0) => None,
Idx::FromEnd(k) => len.checked_sub(k),
}
}
}
enum Seg {
Key(String),
Index(Idx),
}
pub fn path_is_valid(path: &str) -> bool {
parse_path(path).is_some()
}
fn parse_path(path: &str) -> Option<Vec<Seg>> {
let bytes = path.as_bytes();
if bytes.first() != Some(&b'$') {
return None;
}
let mut segs = Vec::new();
let mut i = 1;
while i < bytes.len() {
match bytes[i] {
b'.' => {
let (key, next) = parse_key(bytes, i + 1)?;
segs.push(Seg::Key(key));
i = next;
}
b'[' => {
let start = i + 1;
let mut j = start;
while j < bytes.len() && bytes[j] != b']' {
j += 1;
}
if j >= bytes.len() {
return None; }
let inner = core::str::from_utf8(&bytes[start..j]).ok()?.trim();
segs.push(Seg::Index(parse_index(inner)?));
i = j + 1;
}
_ => return None,
}
}
Some(segs)
}
fn parse_index(inner: &str) -> Option<Idx> {
if let Some(rest) = inner.strip_prefix('#') {
let rest = rest.trim_start();
if rest.is_empty() {
return Some(Idx::FromEnd(0)); }
let k = rest.strip_prefix('-')?.trim_start();
Some(Idx::FromEnd(k.parse::<usize>().ok()?))
} else {
Some(Idx::Abs(inner.parse::<usize>().ok()?))
}
}
fn parse_key(bytes: &[u8], i: usize) -> Option<(String, usize)> {
if bytes.get(i) == Some(&b'"') {
let mut j = i + 1;
let mut s = String::new();
while j < bytes.len() && bytes[j] != b'"' {
s.push(bytes[j] as char);
j += 1;
}
if j >= bytes.len() {
return None;
}
Some((s, j + 1))
} else {
let start = i;
let mut j = i;
while j < bytes.len() && bytes[j] != b'.' && bytes[j] != b'[' {
j += 1;
}
if j == start {
return None;
}
let key = core::str::from_utf8(&bytes[start..j]).ok()?.to_string();
Some((key, j))
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum SetMode {
Set,
Insert,
Replace,
}
fn walk_to_parent<'a>(mut cur: &'a mut Json, segs: &[Seg]) -> Option<&'a mut Json> {
for seg in segs {
cur = match (cur, seg) {
(Json::Object(m), Seg::Key(k)) => {
m.iter_mut().find(|(kk, _)| kk == k).map(|(_, v)| v)?
}
(Json::Array(a), Seg::Index(idx)) => {
let n = idx.resolve_read(a.len())?;
a.get_mut(n)?
}
_ => return None,
};
}
Some(cur)
}
pub fn set_path(root: &mut Json, path: &str, value: Json, mode: SetMode) -> Option<()> {
let segs = parse_path(path)?;
if segs.is_empty() {
if mode != SetMode::Insert {
*root = value; }
return Some(());
}
let (last, parents) = segs.split_last()?;
let Some(cur) = walk_to_parent(root, parents) else {
return Some(()); };
match (cur, last) {
(Json::Object(m), Seg::Key(k)) => {
let slot = m.iter_mut().find(|(kk, _)| kk == k);
match (slot, mode) {
(Some(s), SetMode::Set) | (Some(s), SetMode::Replace) => s.1 = value,
(None, SetMode::Set) | (None, SetMode::Insert) => m.push((k.clone(), value)),
_ => {}
}
}
(Json::Array(a), Seg::Index(idx)) => {
match idx.resolve_read(a.len()) {
Some(n) if n < a.len() => match mode {
SetMode::Set | SetMode::Replace => a[n] = value,
SetMode::Insert => {}
},
_ => {
if matches!(idx, Idx::FromEnd(0))
&& matches!(mode, SetMode::Set | SetMode::Insert)
{
a.push(value);
}
}
}
}
_ => return Some(()),
}
Some(())
}
pub fn remove_path(root: &mut Json, path: &str) -> Option<()> {
let segs = parse_path(path)?;
let (last, parents) = segs.split_last()?; let Some(cur) = walk_to_parent(root, parents) else {
return Some(());
};
match (cur, last) {
(Json::Object(m), Seg::Key(k)) => m.retain(|(kk, _)| kk != k),
(Json::Array(a), Seg::Index(idx)) => {
if let Some(n) = idx.resolve_read(a.len()) {
if n < a.len() {
a.remove(n);
}
}
}
_ => {}
}
Some(())
}
pub fn merge_patch(target: &mut Json, patch: &Json) {
let Json::Object(pm) = patch else {
*target = patch.clone();
return;
};
if !matches!(target, Json::Object(_)) {
*target = Json::Object(Vec::new());
}
let Json::Object(tm) = target else {
return;
};
for (k, pv) in pm {
if matches!(pv, Json::Null) {
tm.retain(|(kk, _)| kk != k);
} else if let Some(slot) = tm.iter_mut().find(|(kk, _)| kk == k) {
merge_patch(&mut slot.1, pv);
} else {
tm.push((k.clone(), Json::Null));
let slot = tm.last_mut().unwrap();
merge_patch(&mut slot.1, pv);
}
}
}