json_eval_rs/jsoneval/
path_utils.rs1use serde_json::Value;
7
8#[inline]
16pub fn normalize_to_json_pointer(path: &str) -> String {
17 if path.is_empty() {
18 return "".to_string();
19 }
20
21 let mut normalized = path.to_string();
22
23 if normalized.starts_with("#/") {
25 normalized = normalized[1..].to_string(); } else if !normalized.starts_with('/') {
27 if normalized.contains('.') {
29 normalized = format!("/{}", normalized.replace('.', "/"));
30 } else {
31 normalized = format!("/{}", normalized);
33 }
34 }
35
36 while normalized.contains("//") {
38 normalized = normalized.replace("//", "/");
39 }
40
41 if normalized == "/" {
43 "".to_string() } else {
45 normalized
46 }
47}
48
49#[inline]
58pub fn dot_notation_to_schema_pointer(path: &str) -> String {
59 if path.starts_with('#') || path.starts_with('/') {
61 return path.to_string();
62 }
63
64 let parts: Vec<&str> = path.split('.').collect();
66 if parts.is_empty() {
67 return "#/".to_string();
68 }
69
70 let mut result = String::from("#/");
73 for (i, part) in parts.iter().enumerate() {
74 if part.eq(&"properties") {
75 continue;
76 }
77
78 if i > 0 {
79 result.push_str("/properties/");
80 }
81 result.push_str(part);
82 }
83
84 result
85}
86
87#[inline]
96pub fn pointer_to_dot_notation(path: &str) -> String {
97 if path.is_empty() {
98 return String::new();
99 }
100
101 if !path.starts_with('#') && !path.starts_with('/') {
103 return path.to_string();
104 }
105
106 let clean_path = if path.starts_with("#/") {
108 &path[2..]
109 } else if path.starts_with('/') {
110 &path[1..]
111 } else if path.starts_with('#') {
112 &path[1..]
113 } else {
114 path
115 };
116
117 clean_path.replace('/', ".")
119}
120
121#[inline]
125pub fn get_value_by_pointer<'a>(data: &'a Value, pointer: &str) -> Option<&'a Value> {
126 if pointer.is_empty() {
127 Some(data)
128 } else {
129 data.pointer(pointer)
130 }
131}
132
133#[inline]
134pub fn get_value_by_pointer_without_properties<'a>(
135 data: &'a Value,
136 pointer: &str,
137) -> Option<&'a Value> {
138 if pointer.is_empty() {
139 Some(data)
140 } else {
141 data.pointer(&pointer.replace("properties/", ""))
142 }
143}
144
145pub fn get_values_by_pointers<'a>(data: &'a Value, pointers: &[String]) -> Vec<Option<&'a Value>> {
147 pointers
148 .iter()
149 .map(|pointer| get_value_by_pointer(data, pointer))
150 .collect()
151}
152
153#[inline]
157pub fn get_array_element<'a>(data: &'a Value, index: usize) -> Option<&'a Value> {
158 data.as_array()?.get(index)
159}
160
161#[inline]
165pub fn get_array_element_by_pointer<'a>(
166 data: &'a Value,
167 pointer: &str,
168 index: usize,
169) -> Option<&'a Value> {
170 get_value_by_pointer(data, pointer)?.as_array()?.get(index)
171}
172
173#[derive(Debug, Clone)]
175pub struct ArrayMetadata {
176 pub pointer: String,
178 pub length: usize,
180 pub column_names: Vec<String>,
182 pub is_uniform: bool,
184}
185
186impl ArrayMetadata {
187 pub fn build(data: &Value, pointer: &str) -> Option<Self> {
189 let array = get_value_by_pointer(data, pointer)?.as_array()?;
190
191 let length = array.len();
192 if length == 0 {
193 return Some(ArrayMetadata {
194 pointer: pointer.to_string(),
195 length: 0,
196 column_names: Vec::new(),
197 is_uniform: true,
198 });
199 }
200
201 let first_element = &array[0];
203 let column_names = if let Value::Object(obj) = first_element {
204 obj.keys().cloned().collect()
205 } else {
206 Vec::new()
207 };
208
209 let is_uniform = if !column_names.is_empty() {
211 array.iter().all(|elem| {
212 if let Value::Object(obj) = elem {
213 obj.keys().len() == column_names.len()
214 && column_names.iter().all(|col| obj.contains_key(col))
215 } else {
216 false
217 }
218 })
219 } else {
220 let first_type = std::mem::discriminant(first_element);
222 array
223 .iter()
224 .all(|elem| std::mem::discriminant(elem) == first_type)
225 };
226
227 Some(ArrayMetadata {
228 pointer: pointer.to_string(),
229 length,
230 column_names,
231 is_uniform,
232 })
233 }
234
235 #[inline]
237 pub fn get_column_value<'a>(
238 &self,
239 data: &'a Value,
240 row_index: usize,
241 column: &str,
242 ) -> Option<&'a Value> {
243 if !self.is_uniform || row_index >= self.length {
244 return None;
245 }
246
247 get_array_element_by_pointer(data, &self.pointer, row_index)?
248 .as_object()?
249 .get(column)
250 }
251
252 #[inline]
254 pub fn is_valid_index(&self, index: usize) -> bool {
255 index < self.length
256 }
257}