NetworkX node-link JSON: shipping attributed graphs without losing your attributes
Node-link JSON in NetworkX makes attributed graphs portable, but default string coercion of node IDs and JSON-only attribute values are the engineering trade-offs to plan for in pipelines.
21 Dec 2025, 10:23 UTC

The problem is shipping an attributed graph out of Python and back in intact
Data pipelines that build attributed graphs in NetworkX often need to hand them off to a web UI, a job queue, or another Python process. The graph has to stay attributed, and the roundtrip must be predictable. Node-link JSON looks like the obvious choice because it is human readable and JavaScript native, but the default NetworkX behavior makes two engineering trade-offs you have to plan for: node identifiers are coerced to strings, and only JSON serializable attribute values survive.
That is the practical decision point. NetworkX stores Graph, DiGraph and MultiGraph as dicts of dicts, which lets you attach arbitrary Python objects to nodes and edges without a schema. The node-link helpers in networkx.json_graph preserve those attribute dictionaries, but they do it inside the constraints of JSON.
How node_link_data and node_link_graph work
NetworkX exposes two functions for the node-link format:
- node_link_data(G) returns a dict with keys "directed", "multigraph", "nodes" and "links" that can be passed to json.dumps.
- node_link_graph(data) reconstructs a NetworkX graph from that dict.
Both support Graph, DiGraph and MultiGraph. Node attributes are emitted as a dict under each node entry, edge attributes as a dict under each link entry. The adjacency representation behind the scenes is a dict of dicts, giving O(1) neighbor lookups and cheap mutable updates for interactive prototyping and medium size in-memory analysis.
Attribute preservation versus interoperability
Node-link JSON is ideal when the consumer is JSON native and you control the attribute schema. Attributes must be JSON serializable: str, int, float, bool, list, dict, None. Datetime, Decimal, custom classes and NumPy scalars require manual conversion before serialization.
For tool interoperability NetworkX also provides GraphML and GEXF read/write. Those formats map attributes to strings and numbers and will drop non-serializable Python objects. The type mapping is different from node-link, which is useful to know when you compare a roundtrip.
Worked example with ID coercion and attribute check
Run locally in a Python REPL or script with networkx installed. No special permissions are required.
import networkx as nx
from networkx.readwrite import json_graph
G = nx.Graph()
G.add_node(1, label="a", weight=0.5)
G.add_node("b", label="b", weight=1.2)
G.add_edge(1, "b", cost=10, tags=["fast"])
data = json_graph.node_link_data(G)
# data is JSON compatible; write with json.dumps if persisting
H = json_graph.node_link_graph(data)
# Practical check for attribute fidelity
nodes_match = all(G.nodes[n] == H.nodes[n] for n in G.nodes)
edges_match = all(G[u][v] == H[u][v] for u, v in G.edges)
print("Nodes equal:", nodes_match)
print("Edges equal:", edges_match)
print("Node IDs in H:", list(H.nodes))
Expected checks: the attribute dicts for nodes and edges should be equal for JSON serializable values. Node IDs will be strings in H because the default encoder stringifies them. The integer 1 becomes "1". That is a silent fidelity loss for pipelines that rely on typed IDs.
Risk: if you rely on non-string IDs, you must provide custom node id encoding and decoding functions to node_link_data and node_link_graph. Risk for attributes: passing a datetime or custom object will raise a TypeError at json.dumps time unless you convert it first.
Trade-off and limitation
The dict-of-dicts model and pure Python implementation make NetworkX excellent for exploration and medium graphs that fit in memory. Memory and speed become limiting on graphs with millions of nodes and edges, and the in-memory model is not out-of-core.
Choose node-link JSON when you need JSON compatibility and you can accept string IDs and JSON serializable attributes. Choose GraphML when you need to exchange with external graph tools and can tolerate stringified attribute types. For typed IDs, keep IDs as strings in the model or implement a reversible encoding layer before serialization.
A practical way to verify your choice is to serialize a representative subgraph, deserialize it, and assert node and edge attribute equality and ID type stability. Add that assertion to your pipeline tests so regressions on attribute handling are caught early.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.