Skip to content

Commit 87f031f

Browse files
authored
Merge pull request #168 from neo4j/allow-non-json-serializable
Convert non-json-serializable properties to strings
2 parents d3ded3d + e7474ba commit 87f031f

File tree

3 files changed

+44
-40
lines changed

3 files changed

+44
-40
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
## Improvements
1414

1515
* Allow for `Node` and `Relationship` inputs to be given as `camelCase` and `SCREAMING_SNAKE_CASE` (in addition to `snake_case`).
16+
* Convert non-json serializable properties to strings in the `render` method, instead of raising an error.
1617

1718

1819
## Other changes

python-wrapper/src/neo4j_viz/nvl.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import uuid
55
from importlib.resources import files
6+
from typing import Union
67

78
from IPython.display import HTML
89

@@ -40,10 +41,28 @@ def __init__(self) -> None:
4041
with screenshot_path.open("r", encoding="utf-8") as file:
4142
self.screenshot_svg = file.read()
4243

43-
def unsupported_field_type_error(self, e: TypeError, entity: str) -> Exception:
44-
if "not JSON serializable" in str(e):
45-
return ValueError(f"A field of a {entity} object is not supported: {str(e)}")
46-
return e
44+
@staticmethod
45+
def _serialize_entity(entity: Union[Node, Relationship]) -> str:
46+
try:
47+
entity_dict = entity.to_dict()
48+
return json.dumps(entity_dict)
49+
except TypeError:
50+
props_as_strings = {}
51+
for k, v in entity_dict["properties"].items():
52+
try:
53+
json.dumps(v)
54+
except TypeError:
55+
props_as_strings[k] = str(v)
56+
entity_dict["properties"].update(props_as_strings)
57+
58+
try:
59+
return json.dumps(entity_dict)
60+
except TypeError as e:
61+
# This should never happen anymore, but just in case
62+
if "not JSON serializable" in str(e):
63+
raise ValueError(f"A field of a {type(entity).__name__} object is not supported: {str(e)}")
64+
else:
65+
raise e
4766

4867
def render(
4968
self,
@@ -54,14 +73,8 @@ def render(
5473
height: str,
5574
show_hover_tooltip: bool,
5675
) -> HTML:
57-
try:
58-
nodes_json = json.dumps([node.to_dict() for node in nodes])
59-
except TypeError as e:
60-
raise self.unsupported_field_type_error(e, "node")
61-
try:
62-
rels_json = json.dumps([rel.to_dict() for rel in relationships])
63-
except TypeError as e:
64-
raise self.unsupported_field_type_error(e, "relationship")
76+
nodes_json = f"[{','.join([self._serialize_entity(node) for node in nodes])}]"
77+
rels_json = f"[{','.join([self._serialize_entity(rel) for rel in relationships])}]"
6578

6679
render_options_json = json.dumps(render_options.to_dict())
6780
container_id = str(uuid.uuid4())

python-wrapper/tests/test_render.py

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from selenium import webdriver
77

88
from neo4j_viz import Node, Relationship, VisualizationGraph
9+
from neo4j_viz.nvl import NVL
910
from neo4j_viz.options import Layout, Renderer
1011

1112
render_cases = {
@@ -64,34 +65,6 @@ def test_basic_render(render_option: dict[str, Any], tmp_path: Path) -> None:
6465
assert not severe_logs, f"Severe logs found: {severe_logs}, all logs: {logs}"
6566

6667

67-
def test_unsupported_field_type() -> None:
68-
with pytest.raises(
69-
ValueError, match="A field of a node object is not supported: Object of type set is not JSON serializable"
70-
):
71-
nodes = [
72-
Node(
73-
id="4:d09f48a4-5fca-421d-921d-a30a896c604d:0", caption="Person", properties={"unsupported": {1, 2, 3}}
74-
),
75-
]
76-
VG = VisualizationGraph(nodes=nodes, relationships=[])
77-
VG.render()
78-
79-
with pytest.raises(
80-
ValueError,
81-
match="A field of a relationship object is not supported: Object of type set is not JSON serializable",
82-
):
83-
relationships = [
84-
Relationship(
85-
source="4:d09f48a4-5fca-421d-921d-a30a896c604d:0",
86-
target="4:d09f48a4-5fca-421d-921d-a30a896c604d:6",
87-
caption="BUYS",
88-
properties={"unsupported": {1, 2, 3}},
89-
),
90-
]
91-
VG = VisualizationGraph(nodes=[], relationships=relationships)
92-
VG.render()
93-
94-
9568
def test_max_allowed_nodes_limit() -> None:
9669
nodes = [Node(id=i) for i in range(10_001)]
9770
VG = VisualizationGraph(nodes=nodes, relationships=[])
@@ -121,3 +94,20 @@ def test_render_warnings() -> None:
12194
"relationships. If you need these features, use the canvas renderer by setting the `renderer` parameter",
12295
):
12396
VG.render(max_allowed_nodes=20_000, renderer=Renderer.WEB_GL)
97+
98+
99+
def test_render_non_json_serializable() -> None:
100+
import datetime
101+
102+
now = datetime.datetime.now()
103+
node = Node(
104+
id=0,
105+
properties={
106+
"non-json-serializable": now,
107+
},
108+
)
109+
assert str(now) in NVL._serialize_entity(node)
110+
111+
VG = VisualizationGraph(nodes=[node], relationships=[])
112+
# Should not raise an error
113+
VG.render()

0 commit comments

Comments
 (0)