use crate::tape::{TapeEntryKind, TapeRef, tape_skip};
pub trait JsonRef<'a>: Sized + Copy {
type Item: JsonRef<'a>;
fn is_null(self) -> bool {
self.as_null().is_some()
}
fn is_bool(self) -> bool {
self.as_bool().is_some()
}
fn is_number(self) -> bool {
self.as_number_str().is_some()
}
fn is_string(self) -> bool {
self.as_str().is_some()
}
fn is_array(self) -> bool;
fn is_object(self) -> bool;
fn as_null(self) -> Option<()>;
fn as_bool(self) -> Option<bool>;
fn as_number_str(self) -> Option<&'a str>;
fn as_str(self) -> Option<&'a str>;
fn as_i64(self) -> Option<i64> {
self.as_number_str()?.parse().ok()
}
fn as_u64(self) -> Option<u64> {
self.as_number_str()?.parse().ok()
}
fn as_f64(self) -> Option<f64> {
self.as_number_str()?.parse().ok()
}
fn get(self, key: &str) -> Option<Self::Item>;
fn index_at(self, i: usize) -> Option<Self::Item>;
fn len(self) -> Option<usize>;
}
impl<'t, 'src: 't> JsonRef<'t> for TapeRef<'t, 'src> {
type Item = Self;
fn is_array(self) -> bool {
self.tape[self.pos].kind() == TapeEntryKind::StartArray
}
fn is_object(self) -> bool {
self.tape[self.pos].kind() == TapeEntryKind::StartObject
}
fn as_null(self) -> Option<()> {
(self.tape[self.pos].kind() == TapeEntryKind::Null).then_some(())
}
fn as_bool(self) -> Option<bool> {
self.tape[self.pos].as_bool()
}
fn as_number_str(self) -> Option<&'t str> {
self.tape[self.pos].as_number()
}
fn as_str(self) -> Option<&'t str> {
self.tape[self.pos].as_string()
}
fn get(self, key: &str) -> Option<Self> {
let end_idx = self.tape[self.pos].as_start_object()?;
let mut i = self.pos + 1;
while i < end_idx {
let k_str: &str = self.tape[i].as_key()?;
let val_pos = i + 1;
if k_str == key {
return Some(TapeRef {
tape: self.tape,
pos: val_pos,
});
}
i = tape_skip(self.tape, val_pos);
}
None
}
fn index_at(self, idx: usize) -> Option<Self> {
let end_idx = self.tape[self.pos].as_start_array()?;
let mut i = self.pos + 1;
let mut count = 0usize;
while i < end_idx {
if count == idx {
return Some(TapeRef {
tape: self.tape,
pos: i,
});
}
i = tape_skip(self.tape, i);
count += 1;
}
None
}
fn len(self) -> Option<usize> {
if let Some(end_idx) = self.tape[self.pos].as_start_array() {
let mut count = 0usize;
let mut i = self.pos + 1;
while i < end_idx {
i = tape_skip(self.tape, i);
count += 1;
}
return Some(count);
}
if let Some(end_idx) = self.tape[self.pos].as_start_object() {
let mut count = 0usize;
let mut i = self.pos + 1;
while i < end_idx {
i = tape_skip(self.tape, i + 1);
count += 1;
}
return Some(count);
}
None
}
}
impl<'a, J: JsonRef<'a>> JsonRef<'a> for Option<J> {
type Item = J::Item;
fn is_array(self) -> bool {
self.is_some_and(|v| v.is_array())
}
fn is_object(self) -> bool {
self.is_some_and(|v| v.is_object())
}
fn as_null(self) -> Option<()> {
self?.as_null()
}
fn as_bool(self) -> Option<bool> {
self?.as_bool()
}
fn as_number_str(self) -> Option<&'a str> {
self?.as_number_str()
}
fn as_str(self) -> Option<&'a str> {
self?.as_str()
}
fn get(self, key: &str) -> Option<J::Item> {
self?.get(key)
}
fn index_at(self, i: usize) -> Option<J::Item> {
self?.index_at(i)
}
fn len(self) -> Option<usize> {
self?.len()
}
}
#[cfg(test)]
mod tests {
use crate::tape::Tape;
use crate::{choose_classifier, classify_u64, classify_ymm, parse_to_tape};
use super::JsonRef;
fn run_tape(json: &'static str) -> Option<Tape<'static>> {
let x = parse_to_tape(json, classify_u64);
let y = parse_to_tape(json, classify_ymm);
let z = parse_to_tape(json, choose_classifier());
assert_eq!(
x.as_ref().map(|t| &t.entries),
z.as_ref().map(|t| &t.entries),
"U64 vs ZMM tape differ for: {json:?}"
);
assert_eq!(
y.as_ref().map(|t| &t.entries),
z.as_ref().map(|t| &t.entries),
"YMM vs ZMM tape differ for: {json:?}"
);
z
}
#[test]
fn jsonref_scalars_tape() {
let t = run_tape("null").unwrap();
let r = t.root().unwrap();
assert!(r.is_null());
assert!(r.as_null().is_some());
let t = run_tape("true").unwrap();
assert_eq!(t.root().unwrap().as_bool(), Some(true));
let t = run_tape("false").unwrap();
assert_eq!(t.root().unwrap().as_bool(), Some(false));
let t = run_tape("42").unwrap();
let r = t.root().unwrap();
assert!(r.is_number());
assert_eq!(r.as_number_str(), Some("42"));
assert_eq!(r.as_i64(), Some(42));
assert_eq!(r.as_u64(), Some(42));
assert_eq!(r.as_f64(), Some(42.0));
let t = run_tape(r#""hello""#).unwrap();
assert_eq!(t.root().unwrap().as_str(), Some("hello"));
}
#[test]
fn jsonref_object_get() {
let src = r#"{"x":1,"y":"hi","z":true}"#;
let t = run_tape(src).unwrap();
let tr = t.root().unwrap();
assert!(tr.is_object());
assert_eq!(tr.get("x").and_then(|r| r.as_i64()), Some(1));
assert_eq!(tr.get("y").and_then(|r| r.as_str()), Some("hi"));
assert_eq!(tr.get("z").and_then(|r| r.as_bool()), Some(true));
assert!(tr.get("missing").is_none());
assert_eq!(tr.len(), Some(3));
}
#[test]
fn jsonref_array_index() {
let src = r#"[1,"two",false,null]"#;
let t = run_tape(src).unwrap();
let tr = t.root().unwrap();
assert!(tr.is_array());
assert_eq!(tr.len(), Some(4));
assert_eq!(tr.index_at(0).and_then(|r| r.as_i64()), Some(1));
assert_eq!(tr.index_at(1).and_then(|r| r.as_str()), Some("two"));
assert_eq!(tr.index_at(2).and_then(|r| r.as_bool()), Some(false));
assert!(tr.index_at(3).unwrap().is_null());
assert!(tr.index_at(4).is_none());
}
#[test]
fn jsonref_nested() {
let src = r#"{"items":[10,20,30],"meta":{"count":3}}"#;
let t = run_tape(src).unwrap();
let tr = t.root().unwrap();
assert_eq!(
tr.get("items")
.and_then(|a| a.index_at(1))
.and_then(|n| n.as_i64()),
Some(20)
);
assert_eq!(
tr.get("meta")
.and_then(|o| o.get("count"))
.and_then(|n| n.as_i64()),
Some(3)
);
}
#[test]
fn jsonref_generic_fn() {
fn first_item<'a, J: JsonRef<'a>>(val: J) -> Option<i64> {
val.index_at(0)?.as_i64()
}
let src = "[7,8,9]";
let t = run_tape(src).unwrap();
assert_eq!(first_item(t.root().unwrap()), Some(7));
}
#[test]
fn jsonref_option_chaining() {
let src = r#"{"a":{"b":{"c":42}},"arr":[10,20,30]}"#;
let t = run_tape(src).unwrap();
let tr = t.root().unwrap();
assert_eq!(tr.get("a").get("b").get("c").as_i64(), Some(42));
assert!(tr.get("a").get("missing").get("c").as_i64().is_none());
assert!(tr.get("none").get("b").as_i64().is_none());
let src2 = r#"{"items":[{"val":1},{"val":2},{"val":3}]}"#;
let t2 = run_tape(src2).unwrap();
assert_eq!(
t2.root()
.unwrap()
.get("items")
.index_at(1)
.get("val")
.as_i64(),
Some(2)
);
}
}