diff --git a/docs/source/api/recipes/recipes.ipynb b/docs/source/api/recipes/recipes.ipynb index e7ff4a0ed..30045c5d9 100644 --- a/docs/source/api/recipes/recipes.ipynb +++ b/docs/source/api/recipes/recipes.ipynb @@ -958,11 +958,13 @@ } ], "source": [ - "import xgi\n", + "import itertools\n", + "\n", "import matplotlib.pyplot as plt\n", - "from matplotlib import gridspec\n", "import numpy as np\n", - "import itertools\n", + "from matplotlib import gridspec\n", + "\n", + "import xgi\n", "\n", "link_color = \"#000000\"\n", "triangle_color = \"#648FFF\"\n", @@ -970,9 +972,11 @@ "pentagon_color = \"#DC267F\"\n", "colors = [link_color, triangle_color, square_color, pentagon_color]\n", "\n", + "\n", "def color_edges(H):\n", " return [colors[i - 2] for i in H.edges.filterby(\"order\", 1, \"gt\").size.aslist()]\n", "\n", + "\n", "links = [[1, 2], [1, 3], [5, 6], [1, 7]]\n", "triangles = [[3, 5, 7], [2, 7, 1], [6, 10, 15]]\n", "squares = [[7, 8, 9, 10]]\n", diff --git a/tests/readwrite/test_json.py b/tests/readwrite/test_json.py index d1e549208..a60b687b7 100644 --- a/tests/readwrite/test_json.py +++ b/tests/readwrite/test_json.py @@ -251,3 +251,15 @@ def test_write_json(edgelist1): assert H2.nodes[2] == {"name": "Ilya"} assert H2.edges[1] == {"weight": 2} assert H2["name"] == "test" + + badH = xgi.Hypergraph() + # duplicate node IDs when casting to a string + badH.add_nodes_from(["2", 2]) + with pytest.raises(XGIError): + xgi.write_json(badH, "test.json") + + badH = xgi.Hypergraph() + # duplicate edge IDs when casting to a string + badH.add_edges_from({"2": [1, 2, 3], 2: [4, 5, 6]}) + with pytest.raises(XGIError): + xgi.write_json(badH, "test.json") diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index f6eb97675..481eb46b0 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -1,5 +1,6 @@ """Read from and write to JSON.""" import json +from collections import Counter from ..convert import dict_to_hypergraph from ..exception import XGIError @@ -18,6 +19,12 @@ def write_json(H, path): path: string The path of the file to read from + Raises + ------ + XGIError + If the node or edge IDs have conflicts after casting + to strings, e.g., node IDs "2" and 2. + """ # initialize empty data data = {} @@ -29,11 +36,31 @@ def write_json(H, path): # get node data try: data["node-data"] = {str(idx): H.nodes[idx] for idx in H.nodes} + + if len(data["node-data"]) != H.num_nodes: + dups = [ + item + for item, count in Counter([str(n) for n in H.nodes]).items() + if count > 1 + ] + raise XGIError( + f"When casting node IDs to strings, ID(s) {', '.join(dups)} have conflicting IDs!" + ) except KeyError: raise XGIError("Node attributes not saved!") try: data["edge-data"] = {str(idx): H.edges[idx] for idx in H.edges} + + if len(data["edge-data"]) != H.num_edges: + dups = [ + item + for item, count in Counter([str(n) for n in H.edges]).items() + if count > 1 + ] + raise XGIError( + f"When casting edge IDs to strings, ID(s) {', '.join(dups)} have conflicting IDs!" + ) except KeyError: raise XGIError("Edge attributes not saved!")