use super::jaq_json::Val;
pub(super) const MAX_JQ_JSON_DEPTH: usize = 100;
#[derive(Debug, Clone, PartialEq)]
pub(super) enum JqJson {
Null,
Bool(bool),
Number(String),
String(String),
Array(Vec<JqJson>),
Object(Vec<(String, JqJson)>),
}
impl JqJson {
pub(super) fn is_null(&self) -> bool {
matches!(self, JqJson::Null)
}
pub(super) fn is_false(&self) -> bool {
matches!(self, JqJson::Bool(false))
}
}
struct JqSeed<'a> {
depth: usize,
max: usize,
too_deep: &'a std::cell::Cell<Option<String>>,
}
impl<'a> JqSeed<'a> {
fn child(&self) -> JqSeed<'a> {
JqSeed {
depth: self.depth + 1,
max: self.max,
too_deep: self.too_deep,
}
}
}
impl<'de> serde::de::DeserializeSeed<'de> for JqSeed<'_> {
type Value = JqJson;
fn deserialize<D: serde::Deserializer<'de>>(self, d: D) -> Result<JqJson, D::Error> {
if self.depth > self.max {
let msg = format!(
"jq: JSON nesting too deep ({} levels, max {})",
self.depth, self.max
);
self.too_deep.set(Some(msg.clone()));
return Err(serde::de::Error::custom(msg));
}
d.deserialize_any(self)
}
}
impl<'de> serde::de::Visitor<'de> for JqSeed<'_> {
type Value = JqJson;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("a JSON value")
}
fn visit_unit<E>(self) -> Result<JqJson, E> {
Ok(JqJson::Null)
}
fn visit_bool<E>(self, b: bool) -> Result<JqJson, E> {
Ok(JqJson::Bool(b))
}
fn visit_i64<E>(self, n: i64) -> Result<JqJson, E> {
Ok(JqJson::Number(n.to_string()))
}
fn visit_u64<E>(self, n: u64) -> Result<JqJson, E> {
Ok(JqJson::Number(n.to_string()))
}
fn visit_f64<E: serde::de::Error>(self, f: f64) -> Result<JqJson, E> {
serde_json::Number::from_f64(f)
.map(|n| JqJson::Number(n.to_string()))
.ok_or_else(|| E::custom("non-finite number"))
}
fn visit_str<E>(self, s: &str) -> Result<JqJson, E> {
Ok(JqJson::String(s.to_owned()))
}
fn visit_string<E>(self, s: String) -> Result<JqJson, E> {
Ok(JqJson::String(s))
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<JqJson, A::Error> {
let mut out = Vec::new();
while let Some(item) = seq.next_element_seed(self.child())? {
out.push(item);
}
Ok(JqJson::Array(out))
}
fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<JqJson, A::Error> {
let mut out: Vec<(String, JqJson)> = Vec::new();
while let Some(key) = map.next_key::<String>()? {
let value = map.next_value_seed(self.child())?;
if let Some(slot) = out.iter_mut().find(|(k, _)| *k == key) {
slot.1 = value;
} else {
out.push((key, value));
}
}
Ok(JqJson::Object(out))
}
}
pub(super) fn parse_json_value(input: &str) -> std::result::Result<JqJson, JsonParseError> {
use serde::de::DeserializeSeed;
let too_deep = std::cell::Cell::new(None);
let mut de = serde_json::Deserializer::from_str(input);
let seed = JqSeed {
depth: 0,
max: MAX_JQ_JSON_DEPTH,
too_deep: &too_deep,
};
let result = seed.deserialize(&mut de).and_then(|v| de.end().map(|()| v));
result.map_err(|e| match too_deep.take() {
Some(msg) => JsonParseError::TooDeep(msg),
None => JsonParseError::Invalid(e.to_string()),
})
}
#[derive(Debug)]
pub(super) enum JsonParseError {
TooDeep(String),
Invalid(String),
}
pub(super) fn jq_to_val(v: &JqJson) -> Val {
match v {
JqJson::Null => Val::Null,
JqJson::Bool(b) => Val::from(*b),
JqJson::Number(s) => {
if let Ok(i) = s.parse::<i64>()
&& let Ok(i) = isize::try_from(i)
{
return Val::from(i);
}
if s.parse::<f64>().is_ok() {
return Val::Num(super::jaq_json::Num::Dec(super::jaq_json::Rc::new(
s.clone(),
)));
}
Val::from(0isize)
}
JqJson::String(s) => Val::from(s.clone()),
JqJson::Array(arr) => arr.iter().map(jq_to_val).collect(),
JqJson::Object(map) => Val::obj(
map.iter()
.map(|(k, v)| (Val::from(k.clone()), jq_to_val(v)))
.collect(),
),
}
}
pub(super) fn val_to_jq_capped(v: &Val, max_bytes: usize) -> Option<JqJson> {
let mut budget = max_bytes;
val_to_jq_budget(v, &mut budget)
}
fn val_to_jq_budget(v: &Val, budget: &mut usize) -> Option<JqJson> {
let cost = match v {
Val::BStr(b) | Val::TStr(b) => b.len().max(1),
_ => 1,
};
*budget = budget.checked_sub(cost)?;
Some(match v {
Val::Null => JqJson::Null,
Val::Bool(b) => JqJson::Bool(*b),
Val::Num(_) => {
let s = format!("{v}");
if serde_json::from_str::<serde_json::Value>(&s).is_ok() {
JqJson::Number(s)
} else {
if let Ok(f) = s.parse::<f64>() {
if f.is_finite() {
JqJson::Number(format_f64_canonical(f))
} else {
JqJson::Null
}
} else {
JqJson::Null
}
}
}
Val::BStr(_) | Val::TStr(_) => {
let displayed = format!("{v}");
match serde_json::from_str::<String>(&displayed) {
Ok(s) => JqJson::String(s),
Err(_) => JqJson::String(displayed),
}
}
Val::Arr(a) => JqJson::Array(
a.iter()
.map(|x| val_to_jq_budget(x, budget))
.collect::<Option<_>>()?,
),
Val::Obj(o) => {
let map: Vec<(String, JqJson)> = o
.iter()
.map(|(k, v)| {
let key = match k {
Val::TStr(_) | Val::BStr(_) => {
let s = format!("{k}");
serde_json::from_str::<String>(&s).unwrap_or(s)
}
_ => format!("{k}"),
};
Some((key, val_to_jq_budget(v, budget)?))
})
.collect::<Option<_>>()?;
JqJson::Object(map)
}
})
}
fn format_f64_canonical(f: f64) -> String {
let s = format!("{f}");
if s.contains('.') || s.contains('e') || s.contains('E') {
s
} else {
format!("{s}.0")
}
}
pub(super) fn parse_json_stream(input: &str) -> std::result::Result<Vec<JqJson>, String> {
match parse_json_stream_partial(input) {
(vals, None) => Ok(vals),
(_, Some(e)) => Err(e),
}
}
pub(super) fn parse_json_stream_partial(input: &str) -> (Vec<JqJson>, Option<String>) {
use serde::de::DeserializeSeed;
let mut vals = Vec::new();
for (start, end) in super::input::value_spans(input) {
let too_deep = std::cell::Cell::new(None);
let mut de = serde_json::Deserializer::from_str(&input[start..end]);
let seed = JqSeed {
depth: 0,
max: MAX_JQ_JSON_DEPTH,
too_deep: &too_deep,
};
match seed.deserialize(&mut de).and_then(|v| de.end().map(|()| v)) {
Ok(v) => vals.push(v),
Err(e) => {
let msg = too_deep
.take()
.unwrap_or_else(|| format!("jq: invalid JSON: {e}"));
return (vals, Some(msg));
}
}
}
(vals, None)
}
#[cfg(test)]
mod tests {
use super::*;
fn one(s: &str) -> JqJson {
parse_json_value(s).unwrap()
}
#[test]
fn round_trip_preserves_float_zero_decimal() {
match one("1.0") {
JqJson::Number(s) => assert_eq!(s, "1.0"),
_ => panic!("expected Number"),
}
}
#[test]
fn integer_stays_integer() {
match one("42") {
JqJson::Number(s) => assert_eq!(s, "42"),
_ => panic!("expected Number"),
}
}
#[test]
fn object_keeps_input_key_order() {
let JqJson::Object(map) = one(r#"{"b":1,"a":2,"c":3}"#) else {
panic!("expected Object");
};
let keys: Vec<&str> = map.iter().map(|(k, _)| k.as_str()).collect();
assert_eq!(keys, ["b", "a", "c"]);
}
#[test]
fn duplicate_key_keeps_first_position_last_value() {
let JqJson::Object(map) = one(r#"{"a":1,"b":2,"a":3}"#) else {
panic!("expected Object");
};
assert_eq!(map.len(), 2);
assert_eq!(map[0].0, "a");
assert!(matches!(&map[0].1, JqJson::Number(n) if n == "3"));
}
#[test]
fn value_depth_limit() {
let ok = format!("{}1{}", "[".repeat(5), "]".repeat(5));
assert!(parse_json_value(&ok).is_ok());
let deep = format!(
"{}1{}",
"[".repeat(MAX_JQ_JSON_DEPTH + 1),
"]".repeat(MAX_JQ_JSON_DEPTH + 1)
);
assert!(matches!(
parse_json_value(&deep),
Err(JsonParseError::TooDeep(_))
));
}
#[test]
fn value_rejects_trailing_garbage() {
assert!(matches!(
parse_json_value("1 2"),
Err(JsonParseError::Invalid(_))
));
}
#[test]
fn stream_depth_error_is_verbatim() {
let deep = format!(
"{}1{}",
"[".repeat(MAX_JQ_JSON_DEPTH + 1),
"]".repeat(MAX_JQ_JSON_DEPTH + 1)
);
let err = parse_json_stream(&deep).unwrap_err();
assert!(err.starts_with("jq: JSON nesting too deep"), "{err}");
}
#[test]
fn stream_reports_syntax_error() {
let err = parse_json_stream("1 {").unwrap_err();
assert!(err.starts_with("jq: invalid JSON:"), "{err}");
}
#[test]
fn parse_json_stream_handles_ndjson() {
let result = parse_json_stream("1\n2\n3").unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn parse_json_stream_empty() {
let result = parse_json_stream("").unwrap();
assert!(result.is_empty());
}
#[test]
fn parse_json_stream_rejects_deep() {
let depth = 150;
let s = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
let result = parse_json_stream(&s);
assert!(result.is_err());
}
}