NetworkX JSON Graph Serialization: Preserving Custom Attribute Types
0 reputation · 24 Sept 2020, 08:05 UTC
0 reputation · 24 Sept 2020, 08:05 UTC
Goal: Ensure that after loading a graph backup stored in JSON format, all node and edge attributes retain their original Python types (e.g., tuples, nested dictionaries, custom objects) without manual post‑processing.
Constraint: NetworkX’s built‑in JSON functions (json_graph.node_link_data and node_link_graph) only serialize values that are native JSON types; any non‑scalar attribute is converted to a string or omitted, leading to silent type loss during round‑trip.
Uncertainty: There is no official mechanism to embed type information or to plug in a custom encoder/decoder that works transparently with the existing readwrite API, leaving users to decide whether to pre‑process attributes, extend the JSON functions, or rely on external validation.
34775 reputation · 24 Sept 2020, 09:54 UTC
The recommended way to keep custom Python types (tuples, nested dicts, objects) intact when using NetworkX’s JSON read/write functions is to wrap non‑JSON‑serializable values in a tagged dictionary before serialization and to restore them with a matching object_hook during deserialization. This approach works with the existing json_graph.node_link_data and node_link_graph functions without modifying NetworkX source code.
json.dumps/json.loads, which natively support only JSON primitives (str, int, float, bool, None, list, dict). Anything else becomes a string or is dropped.JSONEncoder subclass can intercept unsupported types in its default method and replace them with a dict that carries a type tag (e.g., {'__nx_type__':'tuple','value':[1,2]}).object_hook reconstructs the original Python object when it sees the tag.json.dumps/json.loads calls; the NetworkX JSON functions themselves remain unchanged.networkx-json (PyPI) and the nxjson wrapper already implement this pattern and expose convenience to_json/from_json functions.By converting each unsupported attribute into a deterministic, JSON‑compatible representation that includes a reserved key (__nx_type__), the round‑trip preserves both the value and enough metadata to rebuild the original object. Since the tag is unlikely to clash with legitimate attribute names (you can choose a namespaced key like __nx_type__), decoding is safe and unambiguous.
TYPE_TAG = '__nx_type__'.import json
class NetworkXTypeEncoder(json.JSONEncoder):
def default(self, obj):
# Handle common non‑JSON types
if isinstance(obj, tuple):
return {TYPE_TAG: 'tuple', 'value': list(obj)}
if isinstance(obj, set):
return {TYPE_TAG: 'set', 'value': list(obj)}
# Add custom object handling if they provide a factory
if hasattr(obj, '__nx_serialize__'):
return {TYPE_TAG: 'custom', 'class': obj.__class__.__name__,
'data': obj.__nx_serialize__()}
# Fallback to default JSON behavior (will raise TypeError)
return super().default(obj)
def networkx_type_decoder(dct):
if TYPE_TAG in dct:
tag = dct[TYPE_TAG]
if tag == 'tuple':
return tuple(dct['value'])
if tag == 'set':
return set(dct['value'])
if tag == 'custom':
# Example reconstruction; adapt to your class
cls_name = dct['class']
data = dct['data']
# Assume a registry mapping class name to a factory
factory = _CUSTOM_FACTORIES.get(cls_name)
if factory:
return factory(data)
raise ValueError(f'No factory for {cls_name}')
return dct
import networkx as nx
from networkx.readwrite import json_graph
G = nx.Graph()
G.add_node(0, attr=(1, 2), nested={'a': {3, 4}})
# ... add more nodes/edges as needed
data = json_graph.node_link_data(G)
json_str = json.dumps(data, cls=NetworkXTypeEncoder)
loaded_data = json.loads(json_str, object_hook=networkx_type_decoder)
G2 = json_graph.node_link_graph(loaded_data)
# Verify: G2.nodes[0]['attr'] == (1, 2) and type is tuple
# pip install networkx-json
from networkx_json import to_json, from_json
json_str = to_json(G)
G2 = from_json(json_str)
json.dumps (no encoder) – the attribute will appear as a string or be missing, confirming the need for the custom encoder.networkx-json, run its to_json/from_json and verify the same round‑trip result.If your data already uses a key that matches your chosen tag, change the tag to a namespaced string (e.g., __myapp_nx_type__) to avoid collisions.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.