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().expect("Operation failed");
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 .expect("Test: operation failed");
471 temp_file.flush().expect("Operation failed");
472
473 let graph: Graph<i32, f64> =
474 read_json_format(temp_file.path(), false).expect("Operation failed");
475
476 assert_eq!(graph.node_count(), 3);
477 assert_eq!(graph.edge_count(), 2);
478 }
479
480 #[test]
481 fn test_read_json_directed() {
482 let mut temp_file = NamedTempFile::new().expect("Operation failed");
483 writeln!(
484 temp_file,
485 r#"{{
486 "directed": true,
487 "nodes": [
488 {{"id": "1"}},
489 {{"id": "2"}},
490 {{"id": "3"}}
491 ],
492 "edges": [
493 {{"source": "1", "target": "2"}},
494 {{"source": "2", "target": "3"}}
495 ]
496 }}"#
497 )
498 .expect("Test: operation failed");
499 temp_file.flush().expect("Operation failed");
500
501 let graph: DiGraph<i32, f64> =
502 read_json_format_digraph(temp_file.path(), false).expect("Operation failed");
503
504 assert_eq!(graph.node_count(), 3);
505 assert_eq!(graph.edge_count(), 2);
506 }
507
508 #[test]
509 fn test_read_json_weighted() {
510 let mut temp_file = NamedTempFile::new().expect("Operation failed");
511 writeln!(
512 temp_file,
513 r#"{{
514 "directed": false,
515 "nodes": [
516 {{"id": "1"}},
517 {{"id": "2"}},
518 {{"id": "3"}}
519 ],
520 "edges": [
521 {{"source": "1", "target": "2", "weight": 1.5}},
522 {{"source": "2", "target": "3", "weight": 2.0}}
523 ]
524 }}"#
525 )
526 .expect("Test: operation failed");
527 temp_file.flush().expect("Operation failed");
528
529 let graph: Graph<i32, f64> =
530 read_json_format(temp_file.path(), true).expect("Operation failed");
531
532 assert_eq!(graph.node_count(), 3);
533 assert_eq!(graph.edge_count(), 2);
534 }
535
536 #[test]
537 fn test_write_read_roundtrip() {
538 let mut original_graph: Graph<i32, f64> = Graph::new();
539 original_graph
540 .add_edge(1i32, 2i32, 1.5f64)
541 .expect("Operation failed");
542 original_graph
543 .add_edge(2i32, 3i32, 2.0f64)
544 .expect("Operation failed");
545
546 let temp_file = NamedTempFile::new().expect("Operation failed");
547 write_json_format(&original_graph, temp_file.path(), true).expect("Operation failed");
548
549 let read_graph: Graph<i32, f64> =
550 read_json_format(temp_file.path(), true).expect("Operation failed");
551
552 assert_eq!(read_graph.node_count(), original_graph.node_count());
553 assert_eq!(read_graph.edge_count(), original_graph.edge_count());
554 }
555
556 #[test]
557 fn test_digraph_write_read_roundtrip() {
558 let mut original_graph: DiGraph<i32, f64> = DiGraph::new();
559 original_graph
560 .add_edge(1i32, 2i32, 1.5f64)
561 .expect("Operation failed");
562 original_graph
563 .add_edge(2i32, 3i32, 2.0f64)
564 .expect("Operation failed");
565
566 let temp_file = NamedTempFile::new().expect("Operation failed");
567 write_json_format_digraph(&original_graph, temp_file.path(), true)
568 .expect("Operation failed");
569
570 let read_graph: DiGraph<i32, f64> =
571 read_json_format_digraph(temp_file.path(), true).expect("Operation failed");
572
573 assert_eq!(read_graph.node_count(), original_graph.node_count());
574 assert_eq!(read_graph.edge_count(), original_graph.edge_count());
575 }
576
577 #[test]
578 fn test_invalid_json() {
579 let mut temp_file = NamedTempFile::new().expect("Operation failed");
580 writeln!(temp_file, "{{invalid json").expect("Operation failed");
581 temp_file.flush().expect("Operation failed");
582
583 let result: Result<Graph<i32, f64>> = read_json_format(temp_file.path(), false);
584 assert!(result.is_err());
585 }
586
587 #[test]
588 fn test_missing_node_reference() {
589 let mut temp_file = NamedTempFile::new().expect("Operation failed");
590 writeln!(
591 temp_file,
592 r#"{{
593 "directed": false,
594 "nodes": [
595 {{"id": "1"}},
596 {{"id": "2"}}
597 ],
598 "edges": [
599 {{"source": "1", "target": "3"}}
600 ]
601 }}"#
602 )
603 .expect("Test: operation failed");
604 temp_file.flush().expect("Operation failed");
605
606 let result: Result<Graph<i32, f64>> = read_json_format(temp_file.path(), false);
607 assert!(result.is_err());
608 }
609
610 #[test]
611 fn test_directed_graph_mismatch() {
612 let mut temp_file = NamedTempFile::new().expect("Operation failed");
613 writeln!(
614 temp_file,
615 r#"{{
616 "directed": true,
617 "nodes": [
618 {{"id": "1"}},
619 {{"id": "2"}}
620 ],
621 "edges": [
622 {{"source": "1", "target": "2"}}
623 ]
624 }}"#
625 )
626 .expect("Test: operation failed");
627 temp_file.flush().expect("Operation failed");
628
629 let result: Result<Graph<i32, f64>> = read_json_format(temp_file.path(), false);
631 assert!(result.is_err());
632 }
633}