Skip to content

Commit 99fbbe4

Browse files
committed
Convert non-json-serializable properties to strings
1 parent d3ded3d commit 99fbbe4

File tree

3 files changed

+45
-40
lines changed

3 files changed

+45
-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: 27 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 Any, Union
67

78
from IPython.display import HTML
89

@@ -40,10 +41,30 @@ 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]) -> dict[str, Any]:
46+
try:
47+
entity_dict = entity.to_dict()
48+
json.dumps(entity_dict)
49+
return entity_dict
50+
except TypeError:
51+
props_as_strings = {}
52+
for k, v in entity_dict["properties"].items():
53+
try:
54+
json.dumps(v)
55+
except TypeError:
56+
props_as_strings[k] = str(v)
57+
entity_dict["properties"].update(props_as_strings)
58+
59+
try:
60+
json.dumps(entity_dict)
61+
return entity_dict
62+
except TypeError as e:
63+
# This should never happen anymore, but just in case
64+
if "not JSON serializable" in str(e):
65+
raise ValueError(f"A field of a {type(entity).__name__} object is not supported: {str(e)}")
66+
else:
67+
raise e
4768

4869
def render(
4970
self,
@@ -54,14 +75,8 @@ def render(
5475
height: str,
5576
show_hover_tooltip: bool,
5677
) -> 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")
78+
nodes_json = json.dumps([self._serialize_entity(node) for node in nodes])
79+
rels_json = json.dumps([self._serialize_entity(rel) for rel in relationships])
6580

6681
render_options_json = json.dumps(render_options.to_dict())
6782
container_id = str(uuid.uuid4())

python-wrapper/tests/test_render.py

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -64,34 +64,6 @@ def test_basic_render(render_option: dict[str, Any], tmp_path: Path) -> None:
6464
assert not severe_logs, f"Severe logs found: {severe_logs}, all logs: {logs}"
6565

6666

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-
9567
def test_max_allowed_nodes_limit() -> None:
9668
nodes = [Node(id=i) for i in range(10_001)]
9769
VG = VisualizationGraph(nodes=nodes, relationships=[])
@@ -121,3 +93,20 @@ def test_render_warnings() -> None:
12193
"relationships. If you need these features, use the canvas renderer by setting the `renderer` parameter",
12294
):
12395
VG.render(max_allowed_nodes=20_000, renderer=Renderer.WEB_GL)
96+
97+
98+
def test_render_non_json_serializable() -> None:
99+
import datetime
100+
101+
nodes = [
102+
Node(
103+
id=0,
104+
properties={
105+
"non-json-serializable": datetime.datetime.now(),
106+
},
107+
)
108+
]
109+
110+
VG = VisualizationGraph(nodes=nodes, relationships=[])
111+
112+
VG.render()

0 commit comments

Comments
 (0)