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
144#[inline]
148pub fn get_value_by_pointer<'a>(data: &'a Value, pointer: &str) -> Option<&'a Value> {
149 if pointer.is_empty() {
150 Some(data)
151 } else {
152 data.pointer(pointer)
153 }
154}
155
156#[inline]
157pub fn get_value_by_pointer_without_properties<'a>(
158 data: &'a Value,
159 pointer: &str,
160) -> Option<&'a Value> {
161 if pointer.is_empty() {
162 Some(data)
163 } else {
164 data.pointer(&pointer.replace("properties/", ""))
165 }
166}
167
168pub fn get_values_by_pointers<'a>(data: &'a Value, pointers: &[String]) -> Vec<Option<&'a Value>> {
170 pointers
171 .iter()
172 .map(|pointer| get_value_by_pointer(data, pointer))
173 .collect()
174}
175
176#[inline]
180pub fn get_array_element<'a>(data: &'a Value, index: usize) -> Option<&'a Value> {
181 data.as_array()?.get(index)
182}
183
184#[inline]
188pub fn get_array_element_by_pointer<'a>(
189 data: &'a Value,
190 pointer: &str,
191 index: usize,
192) -> Option<&'a Value> {
193 get_value_by_pointer(data, pointer)?.as_array()?.get(index)
194}
195
196#[derive(Debug, Clone)]
198pub struct ArrayMetadata {
199 pub pointer: String,
201 pub length: usize,
203 pub column_names: Vec<String>,
205 pub is_uniform: bool,
207}
208
209impl ArrayMetadata {
210 pub fn build(data: &Value, pointer: &str) -> Option<Self> {
212 let array = get_value_by_pointer(data, pointer)?.as_array()?;
213
214 let length = array.len();
215 if length == 0 {
216 return Some(ArrayMetadata {
217 pointer: pointer.to_string(),
218 length: 0,
219 column_names: Vec::new(),
220 is_uniform: true,
221 });
222 }
223
224 let first_element = &array[0];
226 let column_names = if let Value::Object(obj) = first_element {
227 obj.keys().cloned().collect()
228 } else {
229 Vec::new()
230 };
231
232 let is_uniform = if !column_names.is_empty() {
234 array.iter().all(|elem| {
235 if let Value::Object(obj) = elem {
236 obj.keys().len() == column_names.len()
237 && column_names.iter().all(|col| obj.contains_key(col))
238 } else {
239 false
240 }
241 })
242 } else {
243 let first_type = std::mem::discriminant(first_element);
245 array
246 .iter()
247 .all(|elem| std::mem::discriminant(elem) == first_type)
248 };
249
250 Some(ArrayMetadata {
251 pointer: pointer.to_string(),
252 length,
253 column_names,
254 is_uniform,
255 })
256 }
257
258 #[inline]
260 pub fn get_column_value<'a>(
261 &self,
262 data: &'a Value,
263 row_index: usize,
264 column: &str,
265 ) -> Option<&'a Value> {
266 if !self.is_uniform || row_index >= self.length {
267 return None;
268 }
269
270 get_array_element_by_pointer(data, &self.pointer, row_index)?
271 .as_object()?
272 .get(column)
273 }
274
275 #[inline]
277 pub fn is_valid_index(&self, index: usize) -> bool {
278 index < self.length
279 }
280}