json_plus/
strip.rs

1use enumflags2::{bitflags, BitFlags};
2use serde_json::Value;
3
4/// Specifies the type of strip operation to perform using Bitwise OR eg. Strip::Nulls | Strip::Empties
5#[bitflags]
6#[repr(u8)]
7#[derive(Copy, Clone, Debug, PartialEq)]
8pub enum Strip {
9    Nulls,
10    Empties,
11}
12
13/// Strips the provided value of specified Strip enum type.
14///
15/// Note: This does NOT remove nulls inside arrays unless ALL are null and the [Strip] `Null` | `Empties`
16/// options are set due to the potential for re-ordering indexes where each may have a specific
17/// meaning.
18/// ```rust
19/// use json_plus::{strip, Strip};
20/// use serde_json::json;
21///
22/// fn main() -> Result<(), Box<dyn std::error::Error>> {
23///     let base = json!({"key":"old value", "null":null, "empty":[]});
24///
25///     let stripped = strip(Strip::Nulls | Strip::Empties, base).unwrap();
26///     println!("{}", stripped.to_string());
27///     Ok(())
28/// }
29/// ```
30#[inline]
31pub fn strip(mask: BitFlags<Strip, u8>, mut value: Value) -> Option<Value> {
32    match strip_mut_inner(mask, &mut value) {
33        false => None,
34        true => Some(value),
35    }
36}
37
38fn strip_mut_inner(mask: BitFlags<Strip, u8>, value: &mut Value) -> bool {
39    match value {
40        Value::Null => !mask.intersects(Strip::Nulls),
41        Value::Object(ref mut o) => {
42            o.retain(|_, v| strip_mut_inner(mask, v));
43            !(o.is_empty() && mask.intersects(Strip::Empties))
44        }
45        Value::Array(a) => {
46            // We do NOT remove nulls inside arrays unless ALL are null and null | empties is set
47            // due to the potential for re-ordering indexes where each may have a specific meaning.
48            let mut null_count = 0;
49            for value in a.iter_mut() {
50                if !strip_mut_inner(mask, value) {
51                    *value = Value::Null;
52                    null_count += 1;
53                }
54            }
55            if null_count == a.len() {
56                a.clear();
57            }
58
59            !(a.is_empty() && mask.intersects(Strip::Empties))
60        }
61        _ => true,
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use serde_json::json;
69
70    #[test]
71    fn strip_it() {
72        assert_eq!(strip(Strip::Nulls.into(), json!(null)), None);
73        assert_eq!(strip(Strip::Nulls | Strip::Empties, json!(null)), None);
74        assert_eq!(strip(Strip::Nulls.into(), json!({})), Some(json!({})));
75        assert_eq!(strip(Strip::Nulls | Strip::Empties, json!({})), None);
76        assert_eq!(
77            strip(
78                Strip::Nulls | Strip::Empties,
79                json!({"key":{"value":"value", "null":null}, "arr":[null]})
80            ),
81            Some(json!({"key":{"value":"value"}}))
82        );
83        assert_eq!(
84            strip(
85                Strip::Nulls | Strip::Empties,
86                json!({"key":{"value":"value", "null":null}, "arr":[null, 1]})
87            ),
88            Some(json!({"key":{"value":"value"}, "arr":[null, 1]}))
89        );
90        assert_eq!(
91            strip(
92                Strip::Nulls | Strip::Empties,
93                json!({"key":{"value":"value", "null":null}, "arr":[]})
94            ),
95            Some(json!({"key":{"value":"value"}}))
96        );
97        assert_eq!(
98            strip(
99                Strip::Empties.into(),
100                json!({"key":{"value":null, "null":null}, "arr":[null], "empty":[]})
101            ),
102            Some(json!({"key":{"value":null, "null":null}, "arr":[null]}))
103        );
104    }
105}