jsonpointer_flatten/
lib.rs

1//! Rust library to flatten a JSON object using JSON Pointer field addressing as defined in [IETF RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901).
2use serde::Serialize;
3use serde_json::{json, Map, Result, Value};
4
5type PointerMap = Vec<String>;
6type FlattenedValue = Map<String, Value>;
7
8/// Flatten a JSON string
9///
10/// # Example
11///
12/// ```
13/// let value = r#"
14/// {
15///     "name": "John Smith",
16///     "age": 24,
17///     "address": {
18///         "country": "US",
19///         "zip": "00000"
20///     },
21///     "phones": [ "123", "456" ]
22/// }
23/// "#;
24///
25/// let result = jsonpointer_flatten::from_str(&value);
26/// ```
27pub fn from_str(s: &str) -> Result<Value> {
28    Ok(from_json(&serde_json::from_str::<Value>(s)?))
29}
30
31/// Flatten a JSON value
32///
33/// # Example
34///
35/// ```
36/// use serde_json::json;
37///
38/// let value = json!(
39/// {
40///     "name": "John Smith",
41///     "age": 24,
42///     "address": {
43///         "country": "US",
44///         "zip": "00000"
45///     },
46///     "phones": [ "123", "456" ]
47/// }
48/// );
49///
50/// let result = jsonpointer_flatten::from_json(&value);
51/// ```
52pub fn from_json(value: &Value) -> Value {
53    let mut route = PointerMap::new();
54    let mut target = FlattenedValue::new();
55
56    process(&value, &mut route, &mut target);
57
58    Value::Object(target)
59}
60
61/// Flatten a struct value
62///
63/// # Example
64///
65/// ```
66/// use serde::Serialize;
67///
68/// #[derive(Serialize)]
69/// struct Person<'a> {
70///     name: &'a str,
71///     age: u8
72/// }
73///
74/// let value = Person {
75///     name: "John Smith",
76///     age: 24
77/// };
78///
79/// let result = jsonpointer_flatten::from(&value);
80/// ```
81pub fn from<T>(value: &T) -> Result<Value>
82where
83    T: Serialize,
84{
85    from_str(&serde_json::to_string(value)?)
86}
87
88fn process(value: &Value, route: &mut PointerMap, target: &mut FlattenedValue) {
89    match value {
90        Value::Null => {
91            target.insert(route.concat(), Value::Null);
92        }
93        Value::Bool(b) => {
94            target.insert(route.concat(), Value::Bool(b.clone()));
95        }
96        Value::Number(n) => {
97            target.insert(route.concat(), Value::Number(n.clone()));
98        }
99        Value::String(s) => {
100            target.insert(route.concat(), Value::String(s.clone()));
101        }
102        Value::Array(arr) => {
103            target.insert(route.concat(), json!([]));
104            arr.iter().enumerate().for_each(|(idx, val)| {
105                route.push(format!("{}{}", "/", idx));
106                process(val, route, target);
107            });
108        }
109        Value::Object(obj) => {
110            target.insert(route.concat(), json!({}));
111            for (key, val) in obj {
112                route.push(format!("{}{}", "/", escape(key.as_str())));
113                process(val, route, target);
114            }
115        }
116    }
117    route.pop();
118}
119
120fn escape<'a>(value: &'a str) -> String {
121    value.replace("~", "~0").replace("/", "~1")
122}
123
124#[cfg(test)]
125mod test {
126    use super::*;
127
128    #[test]
129    fn test_serialize() {
130        let result = from_str("{ \"one\": 1 }").unwrap();
131
132        assert!(result.is_object());
133        assert!(result.as_object().unwrap().len() == 2);
134    }
135
136    #[test]
137    fn test_spec_values() {
138        let value = json!(
139            {
140                "foo": ["bar", "baz"],
141                "": 0,
142                "a/b": 1,
143                "c%d": 2,
144                "e^f": 3,
145                "g|h": 4,
146                "i\\j": 5,
147                "k\"l": 6,
148                " ": 7,
149                "m~n": 8
150             }
151        );
152
153        let actual = from_json(&value);
154
155        assert!(actual.is_object());
156
157        assert!(actual.get("/m~0n").unwrap().eq(&json!(8)));
158        assert!(actual.get("/a~1b").unwrap().eq(&json!(1)));
159        assert!(actual.get("/ ").unwrap().eq(&json!(7)));
160    }
161
162    #[test]
163    fn flatten_top_level_array() {
164        let value = json!([true, 42]);
165
166        let actual = from(&value).unwrap();
167
168        assert!(actual.get("").unwrap().eq(&json!([])));
169        assert!(actual.get("/0").unwrap().eq(&json!(true)));
170        assert!(actual.get("/1").unwrap().eq(&json!(42)));
171    }
172
173    #[test]
174    fn test_readme_example() {
175        let value = json!(
176            {
177                "name": "John Smith",
178                "age": 24,
179                "address": {
180                    "country": "US",
181                    "zip": "00000"
182                },
183                "phones": [ "123", "456" ]
184            }
185        );
186
187        let result = from_json(&value);
188
189        assert!(result.is_object());
190    }
191
192    #[test]
193    fn test_null() {
194        let value = json!({ "name": null });
195
196        let result = from_json(&value);
197
198        assert!(result.is_object());
199        assert!(result
200            .as_object()
201            .unwrap()
202            .get("/name")
203            .unwrap()
204            .eq(&json!(Value::Null)));
205    }
206
207    #[test]
208    fn flatten_array() {
209        let value = json!(
210            [
211                1,
212                "name",
213                {
214                    "country": "US",
215                    "zip": "00000"
216                },
217                [ "123", "456" ]
218            ]
219        );
220
221        let result = from_json(&value);
222
223        assert!(result.is_object());
224    }
225
226    #[test]
227    fn flatten_values() {
228        let value = json!(42);
229
230        let result = from_json(&value);
231
232        assert!(result.is_object());
233    }
234
235    #[test]
236    fn flatten_from_str_throws_invalid_json() {
237        let value = "not json";
238
239        let result = from_str(&value);
240
241        assert!(result.is_err());
242    }
243
244    #[test]
245    fn flatten_from_custom_type() {
246        let value = Person {
247            name: "John Smith",
248            age: 24,
249        };
250
251        let result = from(&value);
252
253        assert!(result.is_ok());
254        assert!(result.unwrap().is_object());
255    }
256
257    #[derive(Serialize)]
258    struct Person<'a> {
259        name: &'a str,
260        age: u8,
261    }
262}