json_eval_rs/jsoneval/
path_utils.rs1use serde_json::Value;
2use std::borrow::Cow;
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#[inline]
73pub fn schema_path_to_data_pointer(path: &str) -> Cow<'_, str> {
74 if path.is_empty() {
75 return Cow::Borrowed("");
76 }
77
78 let no_hash = if path.starts_with('#') {
79 &path[1..]
80 } else {
81 path
82 };
83
84 let clean_path = if no_hash.starts_with('/') {
85 &no_hash[1..]
86 } else {
87 no_hash
88 };
89
90 if !clean_path.contains("properties/") && clean_path != "properties" {
91 if path.starts_with('/') && path.len() == clean_path.len() + 1 {
92 return Cow::Borrowed(path);
93 }
94 let mut s = String::with_capacity(clean_path.len() + 1);
95 s.push('/');
96 s.push_str(clean_path);
97 return Cow::Owned(s);
98 }
99
100 let parts = clean_path.split('/');
101 let mut s = String::with_capacity(clean_path.len() + 1);
102 for part in parts {
103 if part.is_empty() || part == "properties" {
104 continue;
105 }
106 s.push('/');
107 s.push_str(part);
108 }
109
110 if s.is_empty() {
111 Cow::Borrowed("")
112 } else {
113 Cow::Owned(s)
114 }
115}
116
117#[inline]
126pub fn dot_notation_to_schema_pointer(path: &str) -> String {
127 if path.starts_with('#') || path.starts_with('/') {
129 return path.to_string();
130 }
131
132 if path.starts_with("properties.") || path.contains(".properties.") {
134 return format!("#/{}", path.replace('.', "/"));
135 }
136
137 let parts: Vec<&str> = path.split('.').collect();
139 if parts.is_empty() {
140 return "#/".to_string();
141 }
142
143 let mut result = String::from("#");
147 for (i, part) in parts.iter().enumerate() {
148 if part.eq(&"properties") {
149 continue;
150 }
151
152 if i > 0 && !path.starts_with('$') {
153 result.push_str("/properties");
154 }
155 result.push_str("/");
156 result.push_str(part);
157 }
158
159 result
160}
161
162#[inline]
171pub fn pointer_to_dot_notation(path: &str) -> String {
172 if path.is_empty() {
173 return String::new();
174 }
175
176 if !path.starts_with('#') && !path.starts_with('/') {
178 return path.to_string();
179 }
180
181 let clean_path = if path.starts_with("#/") {
183 &path[2..]
184 } else if path.starts_with('/') {
185 &path[1..]
186 } else if path.starts_with('#') {
187 &path[1..]
188 } else {
189 path
190 };
191
192 clean_path.replace('/', ".")
194}
195
196pub fn canonicalize_schema_path(path: &str) -> Cow<'_, str> {
206 if path.is_empty() {
207 return Cow::Borrowed("");
208 }
209
210 if path.starts_with("/$") && !path.contains('.') && !path.contains("//") {
212 return Cow::Borrowed(path);
213 }
214
215 let is_system = path.starts_with('$') || path.starts_with("/$") || path.starts_with("#/$");
217
218 let clean_path = if path.starts_with("#/") {
220 &path[2..]
221 } else if path.starts_with('/') {
222 &path[1..]
223 } else if path.starts_with('#') {
224 &path[1..]
225 } else {
226 path
227 };
228
229 if !is_system
232 && !clean_path.contains('.')
233 && !clean_path.contains('/')
234 && !clean_path.is_empty()
235 {
236 if path.starts_with('/') && path.len() == clean_path.len() + 1 {
237 return Cow::Borrowed(path);
238 }
239 let mut s = String::with_capacity(clean_path.len() + 1);
240 s.push('/');
241 s.push_str(clean_path);
242 return Cow::Owned(s);
243 }
244
245 if clean_path.starts_with("properties/")
247 || clean_path.starts_with("properties.")
248 || clean_path.contains("/properties/")
249 || clean_path.contains(".properties.")
250 {
251 let mut s = String::with_capacity(clean_path.len() + 1);
252 s.push('/');
253 for c in clean_path.chars() {
254 if c == '.' {
255 s.push('/');
256 } else {
257 s.push(c);
258 }
259 }
260 if s == path {
261 return Cow::Borrowed(path);
262 } else {
263 return Cow::Owned(s);
264 }
265 }
266
267 let mut result = String::with_capacity(path.len() * 2);
269 result.push('/');
270
271 let parts = clean_path.split(|c| c == '/' || c == '.');
272 let mut first = true;
273
274 for part in parts {
275 if part.is_empty() || part == "properties" {
276 continue;
277 }
278
279 if !first && !is_system {
280 result.push_str("properties/");
281 }
282 result.push_str(part);
283 result.push('/');
284 first = false;
285 }
286
287 if result.len() > 1 {
288 result.pop(); }
290
291 if result == path {
293 Cow::Borrowed(path)
294 } else {
295 Cow::Owned(result)
296 }
297}
298
299#[inline]
303pub fn get_value_by_pointer<'a>(data: &'a Value, pointer: &str) -> Option<&'a Value> {
304 if pointer.is_empty() {
305 Some(data)
306 } else {
307 data.pointer(pointer)
308 }
309}
310
311#[inline]
312pub fn get_value_by_pointer_without_properties<'a>(
313 data: &'a Value,
314 pointer: &str,
315) -> Option<&'a Value> {
316 if pointer.is_empty() {
317 Some(data)
318 } else {
319 data.pointer(&pointer.replace("properties/", ""))
320 }
321}
322
323pub fn get_values_by_pointers<'a>(data: &'a Value, pointers: &[String]) -> Vec<Option<&'a Value>> {
325 pointers
326 .iter()
327 .map(|pointer| get_value_by_pointer(data, pointer))
328 .collect()
329}
330
331#[inline]
335pub fn get_array_element<'a>(data: &'a Value, index: usize) -> Option<&'a Value> {
336 data.as_array()?.get(index)
337}
338
339#[inline]
343pub fn get_array_element_by_pointer<'a>(
344 data: &'a Value,
345 pointer: &str,
346 index: usize,
347) -> Option<&'a Value> {
348 get_value_by_pointer(data, pointer)?.as_array()?.get(index)
349}
350
351#[derive(Debug, Clone)]
353pub struct ArrayMetadata {
354 pub pointer: String,
356 pub length: usize,
358 pub column_names: Vec<String>,
360 pub is_uniform: bool,
362}
363
364impl ArrayMetadata {
365 pub fn build(data: &Value, pointer: &str) -> Option<Self> {
367 let array = get_value_by_pointer(data, pointer)?.as_array()?;
368
369 let length = array.len();
370 if length == 0 {
371 return Some(ArrayMetadata {
372 pointer: pointer.to_string(),
373 length: 0,
374 column_names: Vec::new(),
375 is_uniform: true,
376 });
377 }
378
379 let first_element = &array[0];
381 let column_names = if let Value::Object(obj) = first_element {
382 obj.keys().cloned().collect()
383 } else {
384 Vec::new()
385 };
386
387 let is_uniform = if !column_names.is_empty() {
389 array.iter().all(|elem| {
390 if let Value::Object(obj) = elem {
391 obj.keys().len() == column_names.len()
392 && column_names.iter().all(|col| obj.contains_key(col))
393 } else {
394 false
395 }
396 })
397 } else {
398 let first_type = std::mem::discriminant(first_element);
400 array
401 .iter()
402 .all(|elem| std::mem::discriminant(elem) == first_type)
403 };
404
405 Some(ArrayMetadata {
406 pointer: pointer.to_string(),
407 length,
408 column_names,
409 is_uniform,
410 })
411 }
412
413 #[inline]
415 pub fn get_column_value<'a>(
416 &self,
417 data: &'a Value,
418 row_index: usize,
419 column: &str,
420 ) -> Option<&'a Value> {
421 if !self.is_uniform || row_index >= self.length {
422 return None;
423 }
424
425 get_array_element_by_pointer(data, &self.pointer, row_index)?
426 .as_object()?
427 .get(column)
428 }
429
430 #[inline]
432 pub fn is_valid_index(&self, index: usize) -> bool {
433 index < self.length
434 }
435}