json_eval_rs/jsoneval/
path_utils.rs1use std::borrow::Cow;
2use serde_json::Value;
3
4#[inline]
14pub fn normalize_to_json_pointer(path: &str) -> Cow<'_, str> {
15 if path.is_empty() {
16 return Cow::Borrowed("");
17 }
18
19 if path.starts_with("#/") {
20 let stripped = &path[1..];
21 if !stripped.contains("//") {
22 return Cow::Borrowed(stripped);
23 }
24 }
25
26 if path.starts_with('/') && !path.contains("//") {
27 return if path == "/" {
28 Cow::Borrowed("")
29 } else {
30 Cow::Borrowed(path)
31 };
32 }
33
34 let mut normalized = String::with_capacity(path.len() + 1);
35 let source = if path.starts_with("#/") {
36 &path[1..]
37 } else if !path.starts_with('/') {
38 normalized.push('/');
39 path
40 } else {
41 path
42 };
43
44 let mut prev_slash = normalized.ends_with('/');
45 for ch in source.chars() {
46 let c = if ch == '.' && !path.starts_with('/') && !path.starts_with('#') {
47 '/'
48 } else {
49 ch
50 };
51 if c == '/' {
52 if !prev_slash {
53 normalized.push('/');
54 }
55 prev_slash = true;
56 } else {
57 normalized.push(c);
58 prev_slash = false;
59 }
60 }
61
62 if normalized == "/" {
63 Cow::Borrowed("")
64 } else {
65 Cow::Owned(normalized)
66 }
67}
68
69
70#[inline]
79pub fn dot_notation_to_schema_pointer(path: &str) -> String {
80 if path.starts_with('#') || path.starts_with('/') {
82 return path.to_string();
83 }
84
85 let parts: Vec<&str> = path.split('.').collect();
87 if parts.is_empty() {
88 return "#/".to_string();
89 }
90
91 let mut result = String::from("#");
95 for (i, part) in parts.iter().enumerate() {
96 if part.eq(&"properties") {
97 continue;
98 }
99
100 if i > 0 && !path.starts_with('$') {
101 result.push_str("/properties");
102 }
103 result.push_str("/");
104 result.push_str(part);
105 }
106
107 result
108}
109
110#[inline]
119pub fn pointer_to_dot_notation(path: &str) -> String {
120 if path.is_empty() {
121 return String::new();
122 }
123
124 if !path.starts_with('#') && !path.starts_with('/') {
126 return path.to_string();
127 }
128
129 let clean_path = if path.starts_with("#/") {
131 &path[2..]
132 } else if path.starts_with('/') {
133 &path[1..]
134 } else if path.starts_with('#') {
135 &path[1..]
136 } else {
137 path
138 };
139
140 clean_path.replace('/', ".")
142}
143
144pub fn canonicalize_schema_path(path: &str) -> Cow<'_, str> {
154 if path.is_empty() {
155 return Cow::Borrowed("");
156 }
157
158 if path.starts_with("/$") && !path.contains('.') && !path.contains("//") {
160 return Cow::Borrowed(path);
161 }
162
163 let is_system = path.starts_with('$') || path.starts_with("/$") || path.starts_with("#/$");
165
166 let clean_path = if path.starts_with("#/") {
168 &path[2..]
169 } else if path.starts_with('/') {
170 &path[1..]
171 } else if path.starts_with('#') {
172 &path[1..]
173 } else {
174 path
175 };
176
177 if !is_system
180 && !clean_path.contains('.')
181 && !clean_path.contains('/')
182 && !clean_path.is_empty()
183 {
184 if path.starts_with('/') && path.len() == clean_path.len() + 1 {
185 return Cow::Borrowed(path);
186 }
187 let mut s = String::with_capacity(clean_path.len() + 1);
188 s.push('/');
189 s.push_str(clean_path);
190 return Cow::Owned(s);
191 }
192
193 let mut result = String::with_capacity(path.len() * 2);
195 result.push('/');
196
197 let parts = clean_path.split(|c| c == '/' || c == '.');
198 let mut first = true;
199
200 for part in parts {
201 if part.is_empty() || part == "properties" {
202 continue;
203 }
204
205 if !first && !is_system {
206 result.push_str("properties/");
207 }
208 result.push_str(part);
209 result.push('/');
210 first = false;
211 }
212
213 if result.len() > 1 {
214 result.pop(); }
216
217 if result == path {
219 Cow::Borrowed(path)
220 } else {
221 Cow::Owned(result)
222 }
223}
224
225#[inline]
229pub fn get_value_by_pointer<'a>(data: &'a Value, pointer: &str) -> Option<&'a Value> {
230 if pointer.is_empty() {
231 Some(data)
232 } else {
233 data.pointer(pointer)
234 }
235}
236
237#[inline]
238pub fn get_value_by_pointer_without_properties<'a>(
239 data: &'a Value,
240 pointer: &str,
241) -> Option<&'a Value> {
242 if pointer.is_empty() {
243 Some(data)
244 } else {
245 data.pointer(&pointer.replace("properties/", ""))
246 }
247}
248
249pub fn get_values_by_pointers<'a>(data: &'a Value, pointers: &[String]) -> Vec<Option<&'a Value>> {
251 pointers
252 .iter()
253 .map(|pointer| get_value_by_pointer(data, pointer))
254 .collect()
255}
256
257#[inline]
261pub fn get_array_element<'a>(data: &'a Value, index: usize) -> Option<&'a Value> {
262 data.as_array()?.get(index)
263}
264
265#[inline]
269pub fn get_array_element_by_pointer<'a>(
270 data: &'a Value,
271 pointer: &str,
272 index: usize,
273) -> Option<&'a Value> {
274 get_value_by_pointer(data, pointer)?.as_array()?.get(index)
275}
276
277#[derive(Debug, Clone)]
279pub struct ArrayMetadata {
280 pub pointer: String,
282 pub length: usize,
284 pub column_names: Vec<String>,
286 pub is_uniform: bool,
288}
289
290impl ArrayMetadata {
291 pub fn build(data: &Value, pointer: &str) -> Option<Self> {
293 let array = get_value_by_pointer(data, pointer)?.as_array()?;
294
295 let length = array.len();
296 if length == 0 {
297 return Some(ArrayMetadata {
298 pointer: pointer.to_string(),
299 length: 0,
300 column_names: Vec::new(),
301 is_uniform: true,
302 });
303 }
304
305 let first_element = &array[0];
307 let column_names = if let Value::Object(obj) = first_element {
308 obj.keys().cloned().collect()
309 } else {
310 Vec::new()
311 };
312
313 let is_uniform = if !column_names.is_empty() {
315 array.iter().all(|elem| {
316 if let Value::Object(obj) = elem {
317 obj.keys().len() == column_names.len()
318 && column_names.iter().all(|col| obj.contains_key(col))
319 } else {
320 false
321 }
322 })
323 } else {
324 let first_type = std::mem::discriminant(first_element);
326 array
327 .iter()
328 .all(|elem| std::mem::discriminant(elem) == first_type)
329 };
330
331 Some(ArrayMetadata {
332 pointer: pointer.to_string(),
333 length,
334 column_names,
335 is_uniform,
336 })
337 }
338
339 #[inline]
341 pub fn get_column_value<'a>(
342 &self,
343 data: &'a Value,
344 row_index: usize,
345 column: &str,
346 ) -> Option<&'a Value> {
347 if !self.is_uniform || row_index >= self.length {
348 return None;
349 }
350
351 get_array_element_by_pointer(data, &self.pointer, row_index)?
352 .as_object()?
353 .get(column)
354 }
355
356 #[inline]
358 pub fn is_valid_index(&self, index: usize) -> bool {
359 index < self.length
360 }
361}