Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ nom = "8.0.0"
num-traits = "0.2.19"
ordered-float = { version = "5.1.0", default-features = false }
rand = { version = "0.9.2", features = ["small_rng"] }
serde = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["std"] }
zmij = "1.0"

Expand Down
95 changes: 76 additions & 19 deletions src/functions/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,12 +1053,20 @@ impl RawJsonb<'_> {
/// along with their corresponding key paths. The key path represents the navigation
/// path from the root to reach each scalar value.
///
/// # Arguments
///
/// * `ignore_array` - When true, arrays are treated as leaf values and returned as
/// `Value::Array` without descending into their elements.
///
/// # Returns
///
/// * `Result<Vec<(KeyPaths<'_>, Value<'_>)>>` - A vector of tuples, each containing:
/// - `KeyPaths`: The path to reach the scalar value
/// - `Value`: The scalar value itself
///
/// Empty objects or arrays are treated as leaf values and returned as `Value::Object` or
/// `Value::Array`.
///
/// # Examples
///
/// ```rust
Expand All @@ -1067,7 +1075,7 @@ impl RawJsonb<'_> {
/// let json = r#"{"user": {"name": "Alice", "scores": [85, 92, 78]}}"#;
/// let jsonb = json.parse::<OwnedJsonb>().unwrap();
/// let raw_jsonb = jsonb.as_raw();
/// let result = raw_jsonb.extract_scalar_key_values();
/// let result = raw_jsonb.extract_scalar_key_values(false);
/// assert!(result.is_ok());
/// let result = result.unwrap();
/// assert_eq!(result.len(), 4);
Expand All @@ -1077,18 +1085,43 @@ impl RawJsonb<'_> {
/// // - path: "user", "scores", 1 -> value: 92
/// // - path: "user", "scores", 2 -> value: 78
/// ```
pub fn extract_scalar_key_values(&self) -> Result<Vec<(KeyPaths<'_>, Value<'_>)>> {
///
/// ```rust
/// use jsonb::{OwnedJsonb, Value};
///
/// let json = r#"{"user": {"name": "Alice", "scores": [85, 92, 78]}}"#;
/// let jsonb = json.parse::<OwnedJsonb>().unwrap();
/// let raw_jsonb = jsonb.as_raw();
/// let result = raw_jsonb.extract_scalar_key_values(true).unwrap();
///
/// assert_eq!(result.len(), 2);
/// assert!(result.iter().any(|(_, value)| matches!(value, Value::Array(_))));
/// // Result contains:
/// // - path: "user", "name" -> value: "Alice"
/// // - path: "user", "scores" -> value: [85, 92, 78]
/// ```
pub fn extract_scalar_key_values(
&self,
ignore_array: bool,
) -> Result<Vec<(KeyPaths<'_>, Value<'_>)>> {
let item = JsonbItem::from_raw_jsonb(*self)?;
let mut result = Vec::with_capacity(16);
let mut current_paths = Vec::with_capacity(3);
Self::extract_scalar_key_values_recursive(item, &mut current_paths, &mut result)?;
Self::extract_scalar_key_values_recursive(
item,
ignore_array,
&mut current_paths,
&mut result,
)?;
Ok(result)
}

/// Helper function for `extract_scalar_key_values` that recursively traverses the JSONB structure.
///
/// This function implements a depth-first traversal of the JSONB document, building up the
/// key path as it goes and collecting scalar values when it reaches leaf nodes.
/// Empty objects or arrays are treated as leaf values and returned as `Value::Object` or
/// `Value::Array` instead of being skipped.
///
/// # Arguments
///
Expand All @@ -1101,32 +1134,56 @@ impl RawJsonb<'_> {
/// * `Result<()>` - Success or error during traversal
fn extract_scalar_key_values_recursive<'a>(
current_item: JsonbItem<'a>,
ignore_array: bool,
current_paths: &mut Vec<KeyPath<'a>>,
result: &mut Vec<(KeyPaths<'a>, Value<'a>)>,
) -> Result<()> {
match current_item {
JsonbItem::Raw(raw) => {
let object_iter_opt = ObjectIterator::new(raw)?;
if let Some(mut object_iter) = object_iter_opt {
for object_result in &mut object_iter {
let (key, val_item) = object_result?;
current_paths.push(KeyPath::Name(Cow::Borrowed(key)));
// Recursively handle object values
Self::extract_scalar_key_values_recursive(val_item, current_paths, result)?;
current_paths.pop();
if object_iter.len() > 0 {
for object_result in &mut object_iter {
let (key, val_item) = object_result?;
current_paths.push(KeyPath::Name(Cow::Borrowed(key)));
// Recursively handle object values
Self::extract_scalar_key_values_recursive(
val_item,
ignore_array,
current_paths,
result,
)?;
current_paths.pop();
}
return Ok(());
}
return Ok(());
}
let array_iter_opt = ArrayIterator::new(raw)?;
if let Some(array_iter) = array_iter_opt {
for (index, array_result) in &mut array_iter.enumerate() {
let val_item = array_result?;
current_paths.push(KeyPath::Index(index as i32));
// Recursively handle array values
Self::extract_scalar_key_values_recursive(val_item, current_paths, result)?;
current_paths.pop();
} else if !ignore_array {
let array_iter_opt = ArrayIterator::new(raw)?;
if let Some(array_iter) = array_iter_opt {
if array_iter.len() > 0 {
for (index, array_result) in &mut array_iter.enumerate() {
let val_item = array_result?;
current_paths.push(KeyPath::Index(index as i32));
// Recursively handle array values
Self::extract_scalar_key_values_recursive(
val_item,
ignore_array,
current_paths,
result,
)?;
current_paths.pop();
}
return Ok(());
}
}
}
if !current_paths.is_empty() {
let key_paths = KeyPaths {
paths: current_paths.clone(),
};
let value = raw.to_value()?;
result.push((key_paths, value));
}
}
JsonbItem::Owned(_) => unreachable!(),
_ => {
Expand Down
84 changes: 84 additions & 0 deletions src/keypath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ pub enum KeyPath<'a> {
Name(Cow<'a, str>),
}

/// Represents a set of owned key path chains.
#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct OwnedKeyPaths {
pub paths: Vec<OwnedKeyPath>,
}

/// Represents a valid owned key path.
#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub enum OwnedKeyPath {
/// represents the index of an Array, allow negative indexing.
Index(i32),
/// represents the quoted field name of an Object.
QuotedName(String),
/// represents the field name of an Object.
Name(String),
}

impl Display for KeyPaths<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{{")?;
Expand Down Expand Up @@ -81,6 +98,73 @@ impl Display for KeyPath<'_> {
}
}

impl Display for OwnedKeyPaths {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{{")?;
for (i, path) in self.paths.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{path}")?;
}
write!(f, "}}")?;
Ok(())
}
}

impl Display for OwnedKeyPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
OwnedKeyPath::Index(idx) => {
write!(f, "{idx}")?;
}
OwnedKeyPath::QuotedName(name) => {
write!(f, "\"{name}\"")?;
}
OwnedKeyPath::Name(name) => {
write!(f, "{name}")?;
}
}
Ok(())
}
}

impl<'a> KeyPaths<'a> {
pub fn to_owned(&self) -> OwnedKeyPaths {
OwnedKeyPaths {
paths: self.paths.iter().map(KeyPath::to_owned).collect(),
}
}
}

impl OwnedKeyPaths {
pub fn as_key_paths(&self) -> KeyPaths<'_> {
KeyPaths {
paths: self.paths.iter().map(OwnedKeyPath::as_key_path).collect(),
}
}
}

impl<'a> KeyPath<'a> {
pub fn to_owned(&self) -> OwnedKeyPath {
match self {
KeyPath::Index(idx) => OwnedKeyPath::Index(*idx),
KeyPath::QuotedName(name) => OwnedKeyPath::QuotedName(name.to_string()),
KeyPath::Name(name) => OwnedKeyPath::Name(name.to_string()),
}
}
}

impl OwnedKeyPath {
pub fn as_key_path(&self) -> KeyPath<'_> {
match self {
OwnedKeyPath::Index(idx) => KeyPath::Index(*idx),
OwnedKeyPath::QuotedName(name) => KeyPath::QuotedName(Cow::Borrowed(name.as_str())),
OwnedKeyPath::Name(name) => KeyPath::Name(Cow::Borrowed(name.as_str())),
}
}
}

/// Parsing the input string to key paths.
pub fn parse_key_paths(input: &[u8]) -> Result<KeyPaths<'_>, Error> {
match key_paths(input) {
Expand Down
Loading