1use bids_core::error::Result;
12use bids_core::metadata::BidsMetadata;
13use indexmap::IndexMap;
14use serde_json::Value;
15use std::path::Path;
16
17pub fn read_json_sidecar(path: &Path) -> Result<BidsMetadata> {
23 let contents = std::fs::read_to_string(path)?;
24 let map: IndexMap<String, Value> = serde_json::from_str(&contents)?;
25 let mut md = BidsMetadata::with_source(&path.to_string_lossy());
26 md.update_from_map(map);
27 Ok(md)
28}
29
30pub fn read_json(path: &Path) -> Result<Value> {
36 let contents = std::fs::read_to_string(path)?;
37 let val: Value = serde_json::from_str(&contents)?;
38 Ok(val)
39}
40
41pub fn merge_json_sidecars(sidecars: &[&Path]) -> Result<BidsMetadata> {
51 let mut merged = BidsMetadata::new();
52 for path in sidecars.iter().rev() {
54 let md = read_json_sidecar(path)?;
55 merged.extend(md);
56 }
57 Ok(merged)
58}
59
60pub fn find_sidecars(data_file: &Path, root: &Path) -> Vec<std::path::PathBuf> {
65 let mut sidecars = Vec::new();
66
67 let stem = data_file.file_stem().and_then(|s| s.to_str()).unwrap_or("");
69 let stem = stem.strip_suffix(".tsv").unwrap_or(stem);
71
72 let suffix = stem.rsplit('_').next().unwrap_or("");
73 if suffix.is_empty() {
74 return sidecars;
75 }
76
77 let mut dir = data_file.parent();
79 while let Some(current_dir) = dir {
80 if let Ok(entries) = std::fs::read_dir(current_dir) {
82 for entry in entries.flatten() {
83 let path = entry.path();
84 if path.extension().is_some_and(|e| e == "json") {
85 let json_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
86 let json_suffix = json_stem.rsplit('_').next().unwrap_or("");
87
88 if json_suffix == suffix && is_sidecar_for(&path, data_file) {
89 sidecars.push(path);
90 }
91 }
92 }
93 }
94
95 if current_dir == root {
96 break;
97 }
98 dir = current_dir.parent();
99 }
100
101 sidecars
102}
103
104fn is_sidecar_for(sidecar: &Path, data_file: &Path) -> bool {
110 let sc_stem = sidecar.file_stem().and_then(|s| s.to_str()).unwrap_or("");
111 let df_stem = data_file.file_stem().and_then(|s| s.to_str()).unwrap_or("");
112 let df_stem = df_stem.strip_suffix(".tsv").unwrap_or(df_stem);
114
115 let sc_parts: Vec<&str> = sc_stem.split('_').collect();
116 let df_parts: Vec<&str> = df_stem.split('_').collect();
117
118 for part in &sc_parts {
119 if part.contains('-') {
120 if !df_parts.contains(part) {
122 return false;
123 }
124 }
125 }
128
129 true
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::io::Write;
136
137 #[test]
138 fn test_read_json_sidecar() {
139 let dir = std::env::temp_dir().join("bids_io_json_test");
140 std::fs::create_dir_all(&dir).unwrap();
141 let path = dir.join("sub-01_task-rest_eeg.json");
142 let mut f = std::fs::File::create(&path).unwrap();
143 write!(f, r#"{{"SamplingFrequency": 256, "EEGReference": "Cz"}}"#).unwrap();
144
145 let md = read_json_sidecar(&path).unwrap();
146 assert_eq!(md.get_f64("SamplingFrequency"), Some(256.0));
147 assert_eq!(md.get_str("EEGReference"), Some("Cz"));
148 std::fs::remove_dir_all(&dir).unwrap();
149 }
150}