dts_core/
key.rs

1//! Object key transformation utilities.
2
3use crate::{
4    parsers::flat_key::{self, KeyPart, KeyParts, StringKeyParts},
5    value::ValueExt,
6};
7use rayon::prelude::*;
8use serde_json::{Map, Value};
9use std::collections::BTreeMap;
10use std::iter;
11
12/// Flattens value to an object with flat keys.
13///
14/// ## Examples
15///
16/// Nested map with array:
17///
18/// ```
19/// # use pretty_assertions::assert_eq;
20/// use dts_core::key::flatten_keys;
21/// use serde_json::json;
22///
23/// let value = json!({"foo": {"bar": ["baz", "qux"]}});
24///
25/// let value = flatten_keys(value, "data");
26///
27/// assert_eq!(
28///     value,
29///     json!({
30///         "data": {},
31///         "data.foo": {},
32///         "data.foo.bar": [],
33///         "data.foo.bar[0]": "baz",
34///         "data.foo.bar[1]": "qux"
35///     })
36/// );
37/// ```
38///
39/// Array value with prefix "array":
40///
41/// ```
42/// # use pretty_assertions::assert_eq;
43/// use dts_core::key::flatten_keys;
44/// use serde_json::json;
45///
46/// let value = json!(["foo", "bar", "baz"]);
47///
48/// let value = flatten_keys(value, "array");
49///
50/// assert_eq!(
51///     value,
52///     json!({
53///         "array": [],
54///         "array[0]": "foo",
55///         "array[1]": "bar",
56///         "array[2]": "baz"
57///     })
58/// );
59/// ```
60///
61/// Single primitive value:
62///
63/// ```
64/// # use pretty_assertions::assert_eq;
65/// use dts_core::key::flatten_keys;
66/// use serde_json::json;
67///
68/// let value = json!("foo");
69///
70/// assert_eq!(flatten_keys(value, "data"), json!({"data": "foo"}));
71/// ```
72pub fn flatten_keys<P>(value: Value, prefix: P) -> Value
73where
74    P: AsRef<str>,
75{
76    let mut flattener = KeyFlattener::new(prefix.as_ref());
77    Value::Object(Map::from_iter(flattener.flatten(value)))
78}
79
80/// Recursively expands flat keys to nested objects.
81///
82/// ```
83/// # use pretty_assertions::assert_eq;
84/// use dts_core::key::expand_keys;
85/// use serde_json::json;
86///
87/// let value = json!([{"foo.bar": 1, "foo[\"bar-baz\"]": 2}]);
88/// let expected = json!([{"foo": {"bar": 1, "bar-baz": 2}}]);
89///
90/// assert_eq!(expand_keys(value), expected);
91/// ```
92pub fn expand_keys(value: Value) -> Value {
93    match value {
94        Value::Object(object) => object
95            .into_iter()
96            .collect::<Vec<(String, Value)>>()
97            .into_par_iter()
98            .map(|(key, value)| match flat_key::parse(&key).ok() {
99                Some(mut parts) => {
100                    parts.reverse();
101                    expand_key_parts(&mut parts, value)
102                }
103                None => Value::Object(Map::from_iter(iter::once((key, value)))),
104            })
105            .reduce(
106                || Value::Null,
107                |mut a, mut b| {
108                    a.deep_merge(&mut b);
109                    a
110                },
111            ),
112        Value::Array(array) => Value::Array(array.into_iter().map(expand_keys).collect()),
113        value => value,
114    }
115}
116
117fn expand_key_parts(parts: &mut KeyParts, value: Value) -> Value {
118    match parts.pop() {
119        Some(key) => match key {
120            KeyPart::Ident(ident) => {
121                let mut object = Map::with_capacity(1);
122                object.insert(ident, expand_key_parts(parts, value));
123                Value::Object(object)
124            }
125            KeyPart::Index(index) => {
126                let mut array = vec![Value::Null; index + 1];
127                array[index] = expand_key_parts(parts, value);
128                Value::Array(array)
129            }
130        },
131        None => value,
132    }
133}
134
135struct KeyFlattener<'a> {
136    prefix: &'a str,
137    stack: StringKeyParts,
138}
139
140impl<'a> KeyFlattener<'a> {
141    fn new(prefix: &'a str) -> Self {
142        Self {
143            prefix,
144            stack: StringKeyParts::new(),
145        }
146    }
147
148    fn flatten(&mut self, value: Value) -> BTreeMap<String, Value> {
149        let mut map = BTreeMap::new();
150        self.stack.push_ident(self.prefix);
151        self.flatten_value(&mut map, value);
152        self.stack.pop();
153        map
154    }
155
156    fn flatten_value(&mut self, map: &mut BTreeMap<String, Value>, value: Value) {
157        match value {
158            Value::Array(array) => {
159                map.insert(self.key(), Value::Array(Vec::new()));
160                for (index, value) in array.into_iter().enumerate() {
161                    self.stack.push_index(index);
162                    self.flatten_value(map, value);
163                    self.stack.pop();
164                }
165            }
166            Value::Object(object) => {
167                map.insert(self.key(), Value::Object(Map::new()));
168                for (key, value) in object.into_iter() {
169                    self.stack.push_ident(&key);
170                    self.flatten_value(map, value);
171                    self.stack.pop();
172                }
173            }
174            value => {
175                map.insert(self.key(), value);
176            }
177        }
178    }
179
180    fn key(&self) -> String {
181        self.stack.to_string()
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use pretty_assertions::assert_eq;
189    use serde_json::json;
190
191    #[test]
192    fn test_expand_keys() {
193        let value = json!({
194            "data": {},
195            "data.foo": {},
196            "data.foo.bar": [],
197            "data.foo.bar[0]": "baz",
198            "data.foo.bar[1]": "qux"
199        });
200
201        assert_eq!(
202            expand_keys(value),
203            json!({"data": {"foo": {"bar": ["baz", "qux"]}}})
204        );
205    }
206
207    #[test]
208    fn test_flatten_keys() {
209        let value = json!({"foo": {"bar": ["baz", "qux"]}});
210
211        assert_eq!(
212            flatten_keys(value, "data"),
213            json!({
214                "data": {},
215                "data.foo": {},
216                "data.foo.bar": [],
217                "data.foo.bar[0]": "baz",
218                "data.foo.bar[1]": "qux"
219            })
220        );
221    }
222}