1use std::collections::HashMap;
79use std::fs::File;
80use std::io::{BufReader, Write};
81use std::path::Path;
82use std::str::FromStr;
83
84use serde::{Deserialize, Serialize};
85
86use crate::base::{DiGraph, EdgeWeight, Graph, Node};
87use crate::error::{GraphError, Result};
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct JsonNode {
92 pub id: String,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub label: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub data: Option<serde_json::Value>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct JsonEdge {
105 pub source: String,
107 pub target: String,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub weight: Option<f64>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub label: Option<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub data: Option<serde_json::Value>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct JsonGraph {
123 #[serde(default = "default_directed")]
125 pub directed: bool,
126 pub nodes: Vec<JsonNode>,
128 pub edges: Vec<JsonEdge>,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub metadata: Option<serde_json::Value>,
133}
134
135#[allow(dead_code)]
137fn default_directed() -> bool {
138 false
139}
140
141#[allow(dead_code)]
161pub fn read_json_format<N, E, P>(path: P, weighted: bool) -> Result<Graph<N, E>>
162where
163 N: Node + std::fmt::Debug + FromStr + Clone,
164 E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
165 P: AsRef<Path>,
166{
167 let file = File::open(path)?;
168 let reader = BufReader::new(file);
169 let json_graph: JsonGraph = serde_json::from_reader(reader)
170 .map_err(|e| GraphError::Other(format!("Failed to parse JSON: {e}")))?;
171
172 if json_graph.directed {
174 return Err(GraphError::Other(
175 "JSON file contains a directed graph, but undirected graph was requested".to_string(),
176 ));
177 }
178
179 let mut graph = Graph::new();
180
181 let mut node_map = HashMap::new();
183 for json_node in &json_graph.nodes {
184 let node = N::from_str(&json_node.id)
185 .map_err(|_| GraphError::Other(format!("Failed to parse node ID: {}", json_node.id)))?;
186 node_map.insert(json_node.id.clone(), node);
187 }
188
189 for json_edge in &json_graph.edges {
191 let source_node = node_map.get(&json_edge.source).ok_or_else(|| {
192 GraphError::Other(format!(
193 "Edge references unknown source node: {}",
194 json_edge.source
195 ))
196 })?;
197
198 let target_node = node_map.get(&json_edge.target).ok_or_else(|| {
199 GraphError::Other(format!(
200 "Edge references unknown target node: {}",
201 json_edge.target
202 ))
203 })?;
204
205 let weight = if weighted {
207 if let Some(w) = json_edge.weight {
208 E::from_str(&w.to_string())
209 .map_err(|_| GraphError::Other(format!("Failed to parse edge weight: {w}")))?
210 } else {
211 E::default()
212 }
213 } else {
214 E::default()
215 };
216
217 graph.add_edge(source_node.clone(), target_node.clone(), weight)?;
219 }
220
221 Ok(graph)
222}
223
224#[allow(dead_code)]
236pub fn read_json_format_digraph<N, E, P>(path: P, weighted: bool) -> Result<DiGraph<N, E>>
237where
238 N: Node + std::fmt::Debug + FromStr + Clone,
239 E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
240 P: AsRef<Path>,
241{
242 let file = File::open(path)?;
243 let reader = BufReader::new(file);
244 let json_graph: JsonGraph = serde_json::from_reader(reader)
245 .map_err(|e| GraphError::Other(format!("Failed to parse JSON: {e}")))?;
246
247 let mut graph = DiGraph::new();
248
249 let mut node_map = HashMap::new();
251 for json_node in &json_graph.nodes {
252 let node = N::from_str(&json_node.id)
253 .map_err(|_| GraphError::Other(format!("Failed to parse node ID: {}", json_node.id)))?;
254 node_map.insert(json_node.id.clone(), node);
255 }
256
257 for json_edge in &json_graph.edges {
259 let source_node = node_map.get(&json_edge.source).ok_or_else(|| {
260 GraphError::Other(format!(
261 "Edge references unknown source node: {}",
262 json_edge.source
263 ))
264 })?;
265
266 let target_node = node_map.get(&json_edge.target).ok_or_else(|| {
267 GraphError::Other(format!(
268 "Edge references unknown target node: {}",
269 json_edge.target
270 ))
271 })?;
272
273 let weight = if weighted {
275 if let Some(w) = json_edge.weight {
276 E::from_str(&w.to_string())
277 .map_err(|_| GraphError::Other(format!("Failed to parse edge weight: {w}")))?
278 } else {
279 E::default()
280 }
281 } else {
282 E::default()
283 };
284
285 graph.add_edge(source_node.clone(), target_node.clone(), weight)?;
287 }
288
289 Ok(graph)
290}
291
292#[allow(dead_code)]
305pub fn write_json_format<N, E, Ix, P>(
306 graph: &Graph<N, E, Ix>,
307 path: P,
308 weighted: bool,
309) -> Result<()>
310where
311 N: Node + std::fmt::Debug + std::fmt::Display + Clone,
312 E: EdgeWeight
313 + std::marker::Copy
314 + std::fmt::Debug
315 + std::default::Default
316 + std::fmt::Display
317 + Clone,
318 Ix: petgraph::graph::IndexType,
319 P: AsRef<Path>,
320{
321 let mut file = File::create(path)?;
322
323 let nodes: Vec<JsonNode> = graph
325 .nodes()
326 .iter()
327 .map(|node| JsonNode {
328 id: node.to_string(),
329 label: None,
330 data: None,
331 })
332 .collect();
333
334 let edges: Vec<JsonEdge> = graph
336 .edges()
337 .iter()
338 .map(|edge| JsonEdge {
339 source: edge.source.to_string(),
340 target: edge.target.to_string(),
341 weight: if weighted {
342 edge.weight.to_string().parse::<f64>().ok()
344 } else {
345 None
346 },
347 label: None,
348 data: None,
349 })
350 .collect();
351
352 let json_graph = JsonGraph {
354 directed: false,
355 nodes,
356 edges,
357 metadata: None,
358 };
359
360 let json_string = serde_json::to_string_pretty(&json_graph)
362 .map_err(|e| GraphError::Other(format!("Failed to serialize JSON: {e}")))?;
363
364 write!(file, "{json_string}")?;
365
366 Ok(())
367}
368
369#[allow(dead_code)]
382pub fn write_json_format_digraph<N, E, Ix, P>(
383 graph: &DiGraph<N, E, Ix>,
384 path: P,
385 weighted: bool,
386) -> Result<()>
387where
388 N: Node + std::fmt::Debug + std::fmt::Display + Clone,
389 E: EdgeWeight
390 + std::marker::Copy
391 + std::fmt::Debug
392 + std::default::Default
393 + std::fmt::Display
394 + Clone,
395 Ix: petgraph::graph::IndexType,
396 P: AsRef<Path>,
397{
398 let mut file = File::create(path)?;
399
400 let nodes: Vec<JsonNode> = graph
402 .nodes()
403 .iter()
404 .map(|node| JsonNode {
405 id: node.to_string(),
406 label: None,
407 data: None,
408 })
409 .collect();
410
411 let edges: Vec<JsonEdge> = graph
413 .edges()
414 .iter()
415 .map(|edge| JsonEdge {
416 source: edge.source.to_string(),
417 target: edge.target.to_string(),
418 weight: if weighted {
419 edge.weight.to_string().parse::<f64>().ok()
421 } else {
422 None
423 },
424 label: None,
425 data: None,
426 })
427 .collect();
428
429 let json_graph = JsonGraph {
431 directed: true,
432 nodes,
433 edges,
434 metadata: None,
435 };
436
437 let json_string = serde_json::to_string_pretty(&json_graph)
439 .map_err(|e| GraphError::Other(format!("Failed to serialize JSON: {e}")))?;
440
441 write!(file, "{json_string}")?;
442
443 Ok(())
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449 use std::io::Write;
450 use tempfile::NamedTempFile;
451
452 #[test]
453 fn test_read_json_undirected() {
454 let mut temp_file = NamedTempFile::new().unwrap();
455 writeln!(
456 temp_file,
457 r#"{{
458 "directed": false,
459 "nodes": [
460 {{"id": "1"}},
461 {{"id": "2"}},
462 {{"id": "3"}}
463 ],
464 "edges": [
465 {{"source": "1", "target": "2"}},
466 {{"source": "2", "target": "3"}}
467 ]
468 }}"#
469 )
470 .unwrap();
471 temp_file.flush().unwrap();
472
473 let graph: Graph<i32, f64> = read_json_format(temp_file.path(), false).unwrap();
474
475 assert_eq!(graph.node_count(), 3);
476 assert_eq!(graph.edge_count(), 2);
477 }
478
479 #[test]
480 fn test_read_json_directed() {
481 let mut temp_file = NamedTempFile::new().unwrap();
482 writeln!(
483 temp_file,
484 r#"{{
485 "directed": true,
486 "nodes": [
487 {{"id": "1"}},
488 {{"id": "2"}},
489 {{"id": "3"}}
490 ],
491 "edges": [
492 {{"source": "1", "target": "2"}},
493 {{"source": "2", "target": "3"}}
494 ]
495 }}"#
496 )
497 .unwrap();
498 temp_file.flush().unwrap();
499
500 let graph: DiGraph<i32, f64> = read_json_format_digraph(temp_file.path(), false).unwrap();
501
502 assert_eq!(graph.node_count(), 3);
503 assert_eq!(graph.edge_count(), 2);
504 }
505
506 #[test]
507 fn test_read_json_weighted() {
508 let mut temp_file = NamedTempFile::new().unwrap();
509 writeln!(
510 temp_file,
511 r#"{{
512 "directed": false,
513 "nodes": [
514 {{"id": "1"}},
515 {{"id": "2"}},
516 {{"id": "3"}}
517 ],
518 "edges": [
519 {{"source": "1", "target": "2", "weight": 1.5}},
520 {{"source": "2", "target": "3", "weight": 2.0}}
521 ]
522 }}"#
523 )
524 .unwrap();
525 temp_file.flush().unwrap();
526
527 let graph: Graph<i32, f64> = read_json_format(temp_file.path(), true).unwrap();
528
529 assert_eq!(graph.node_count(), 3);
530 assert_eq!(graph.edge_count(), 2);
531 }
532
533 #[test]
534 fn test_write_read_roundtrip() {
535 let mut original_graph: Graph<i32, f64> = Graph::new();
536 original_graph.add_edge(1i32, 2i32, 1.5f64).unwrap();
537 original_graph.add_edge(2i32, 3i32, 2.0f64).unwrap();
538
539 let temp_file = NamedTempFile::new().unwrap();
540 write_json_format(&original_graph, temp_file.path(), true).unwrap();
541
542 let read_graph: Graph<i32, f64> = read_json_format(temp_file.path(), true).unwrap();
543
544 assert_eq!(read_graph.node_count(), original_graph.node_count());
545 assert_eq!(read_graph.edge_count(), original_graph.edge_count());
546 }
547
548 #[test]
549 fn test_digraph_write_read_roundtrip() {
550 let mut original_graph: DiGraph<i32, f64> = DiGraph::new();
551 original_graph.add_edge(1i32, 2i32, 1.5f64).unwrap();
552 original_graph.add_edge(2i32, 3i32, 2.0f64).unwrap();
553
554 let temp_file = NamedTempFile::new().unwrap();
555 write_json_format_digraph(&original_graph, temp_file.path(), true).unwrap();
556
557 let read_graph: DiGraph<i32, f64> =
558 read_json_format_digraph(temp_file.path(), true).unwrap();
559
560 assert_eq!(read_graph.node_count(), original_graph.node_count());
561 assert_eq!(read_graph.edge_count(), original_graph.edge_count());
562 }
563
564 #[test]
565 fn test_invalid_json() {
566 let mut temp_file = NamedTempFile::new().unwrap();
567 writeln!(temp_file, "{{invalid json").unwrap();
568 temp_file.flush().unwrap();
569
570 let result: Result<Graph<i32, f64>> = read_json_format(temp_file.path(), false);
571 assert!(result.is_err());
572 }
573
574 #[test]
575 fn test_missing_node_reference() {
576 let mut temp_file = NamedTempFile::new().unwrap();
577 writeln!(
578 temp_file,
579 r#"{{
580 "directed": false,
581 "nodes": [
582 {{"id": "1"}},
583 {{"id": "2"}}
584 ],
585 "edges": [
586 {{"source": "1", "target": "3"}}
587 ]
588 }}"#
589 )
590 .unwrap();
591 temp_file.flush().unwrap();
592
593 let result: Result<Graph<i32, f64>> = read_json_format(temp_file.path(), false);
594 assert!(result.is_err());
595 }
596
597 #[test]
598 fn test_directed_graph_mismatch() {
599 let mut temp_file = NamedTempFile::new().unwrap();
600 writeln!(
601 temp_file,
602 r#"{{
603 "directed": true,
604 "nodes": [
605 {{"id": "1"}},
606 {{"id": "2"}}
607 ],
608 "edges": [
609 {{"source": "1", "target": "2"}}
610 ]
611 }}"#
612 )
613 .unwrap();
614 temp_file.flush().unwrap();
615
616 let result: Result<Graph<i32, f64>> = read_json_format(temp_file.path(), false);
618 assert!(result.is_err());
619 }
620}