fdg_sim/
json.rs

1//!
2//! Hyperedges aren't implemented, but basic graphs like should work:
3//! ```json ignore
4//! {
5//!     "graph": {
6//!         "nodes": {
7//!             "1": {},
8//!             "2": {},
9//!             "3": {},
10//!             "4": {},
11//!             "5": {},
12//!             "6": {},
13//!             "7": {},
14//!             "8": {}
15//!         },
16//!         "edges": [
17//!             {
18//!                 "source": "1",
19//!                 "target": "2"
20//!             },
21//!             {
22//!                 "source": "2",
23//!                 "target": "3"
24//!             },
25//!             {
26//!                 "source": "3",
27//!                 "target": "4"
28//!             },
29//!             {
30//!                 "source": "4",
31//!                 "target": "1"
32//!             },
33//!             {
34//!                 "source": "5",
35//!                 "target": "6"
36//!             },
37//!             {
38//!                 "source": "6",
39//!                 "target": "7"
40//!             },
41//!             {
42//!                 "source": "7",
43//!                 "target": "8"
44//!             },
45//!             {
46//!                 "source": "8",
47//!                 "target": "5"
48//!             },
49//!             {
50//!                 "source": "1",
51//!                 "target": "5"
52//!             },
53//!             {
54//!                 "source": "2",
55//!                 "target": "6"
56//!             },
57//!             {
58//!                 "source": "3",
59//!                 "target": "7"
60//!             },
61//!             {
62//!                 "source": "4",
63//!                 "target": "8"
64//!             }
65//!         ]
66//!     }
67//! }
68//! ```
69
70use std::{collections::HashMap, error::Error, fmt};
71
72use petgraph::{
73    graph::NodeIndex,
74    visit::{EdgeRef, IntoEdgeReferences},
75};
76use serde::{Deserialize, Serialize};
77use serde_json::{Map, Value};
78
79use crate::{ForceGraph, ForceGraphHelper};
80
81/// Possible errors returned by the functions in the module.
82#[derive(Debug)]
83pub enum JsonParseError {
84    BadFormatting(serde_json::Error),
85    HyperEdges,
86    NodeNotFound(String),
87}
88
89impl fmt::Display for JsonParseError {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Self::BadFormatting(err) => write!(f, "Input not JSON: {err}"),
93            Self::HyperEdges => write!(f, "Graphs with hyperedges are not supported"),
94            Self::NodeNotFound(n) => write!(f, "Node {n} not defined in graph"),
95        }
96    }
97}
98
99impl Error for JsonParseError {}
100
101#[derive(Serialize, Deserialize, Debug)]
102struct JsonGraph {
103    pub graph: InnerJsonGraph,
104}
105
106#[derive(Serialize, Deserialize, Debug)]
107struct InnerJsonGraph {
108    pub nodes: HashMap<String, JsonNode>,
109    pub edges: Option<Vec<JsonEdge>>,
110    // will return an error if you use hyperedges
111    #[serde(rename = "hyperedges")]
112    pub _hyperedges: Option<Value>,
113    #[serde(rename = "id")]
114    pub _id: Option<Value>,
115    #[serde(rename = "type")]
116    pub _type: Option<Value>,
117    #[serde(rename = "label")]
118    pub _label: Option<Value>,
119    #[serde(rename = "directed")]
120    pub _directed: Option<Value>,
121    #[serde(rename = "metadata")]
122    pub _metadata: Option<Value>,
123}
124
125#[derive(Serialize, Deserialize, Debug)]
126struct JsonNode {
127    pub label: Option<String>,
128    pub metadata: Option<Value>,
129}
130
131#[derive(Serialize, Deserialize, Debug)]
132struct JsonEdge {
133    pub source: String,
134    pub target: String,
135    #[serde(default)]
136    pub metadata: Value,
137}
138
139/// Create a json value from a [`ForceGraph`].
140pub fn graph_to_json<N: Serialize, E: Serialize>(
141    graph: &ForceGraph<N, E>,
142) -> Result<Value, serde_json::Error> {
143    let mut nodes: HashMap<String, Value> = HashMap::new();
144    let mut edges: Vec<JsonEdge> = Vec::new();
145
146    for node in graph.node_weights() {
147        let metadata: Value = serde_json::to_value(&node.data)?;
148
149        let jnode = JsonNode {
150            label: None,
151            metadata: Some(metadata),
152        };
153
154        let weight = serde_json::to_value(&jnode)?;
155
156        nodes.insert(node.name.to_owned(), weight);
157    }
158
159    for edge in graph.edge_references() {
160        let edge = JsonEdge {
161            source: graph[edge.source()].name.to_owned(),
162            target: graph[edge.target()].name.to_owned(),
163            metadata: serde_json::to_value(edge.weight())?,
164        };
165
166        edges.push(edge);
167    }
168
169    let mut outer_graph: Map<String, Value> = Map::new();
170
171    let mut inner_graph: Map<String, Value> = Map::new();
172
173    inner_graph.insert("nodes".to_string(), serde_json::to_value(&nodes)?);
174    inner_graph.insert("edges".to_string(), serde_json::to_value(&edges)?);
175
176    outer_graph.insert("graph".to_string(), serde_json::to_value(&inner_graph)?);
177
178    Ok(Value::Object(outer_graph))
179}
180
181/// Get a [`ForceGraph`] from a json string.
182pub fn graph_from_json(json: impl AsRef<str>) -> Result<ForceGraph<Value, Value>, JsonParseError> {
183    let mut graph: ForceGraph<Value, Value> = ForceGraph::default();
184
185    let json: JsonGraph = match serde_json::from_str(json.as_ref()) {
186        Ok(json) => json,
187        Err(err) => return Err(JsonParseError::BadFormatting(err)),
188    };
189
190    if json.graph._hyperedges.is_some() {
191        return Err(JsonParseError::HyperEdges);
192    }
193
194    for (name, data) in json.graph.nodes {
195        let data = match serde_json::to_value(&data) {
196            Ok(data) => data,
197            Err(err) => return Err(JsonParseError::BadFormatting(err)),
198        };
199
200        graph.add_force_node(name, data);
201    }
202
203    if let Some(edges) = json.graph.edges {
204        for edge in edges {
205            let source = match index_from_name(&edge.source, &graph) {
206                Some(source) => source,
207                None => return Err(JsonParseError::NodeNotFound(edge.source)),
208            };
209
210            let target = match index_from_name(&edge.target, &graph) {
211                Some(source) => source,
212                None => return Err(JsonParseError::NodeNotFound(edge.target)),
213            };
214
215            graph.add_edge(source, target, edge.metadata);
216        }
217    }
218
219    Ok(graph)
220}
221
222fn index_from_name(name: impl AsRef<str>, graph: &ForceGraph<Value, Value>) -> Option<NodeIndex> {
223    let name = name.as_ref().to_string();
224
225    graph.node_indices().find(|&idx| name == graph[idx].name)
226}