jsonpointer_flatten/
lib.rs1use serde::Serialize;
3use serde_json::{json, Map, Result, Value};
4
5type PointerMap = Vec<String>;
6type FlattenedValue = Map<String, Value>;
7
8pub fn from_str(s: &str) -> Result<Value> {
28 Ok(from_json(&serde_json::from_str::<Value>(s)?))
29}
30
31pub 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
61pub 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}