Repository: networkx/networkx

Issue:
networkx.readwrite.json_graph bug in serializing MultiGraphs
``` python
import networkx as nx
from networkx.readwrite import json_graph

edges = [(0, 1, {'name': 'a', 'value': 1}),
         (0, 1, {'name': 'b', 'value': 2}),
         (0, 2, {'name': 'a', 'value': 0})]

G = nx.MultiGraph()

for source, target, attr in edges:
    G.add_edge(source, target, key=attr['name'], attr_dict=attr)

dct = json_graph.adjacency_data(G)
H = json_graph.adjacency_graph(dct)
assert sorted(H.nodes()) == sorted(G.nodes())
assert sorted(H.edges()) == sorted(G.edges())
```

Produces:

``` python
Traceback (most recent call last):
  File "nx_bug.py", line 16, in <module>
    assert sorted(H.edges()) == sorted(G.edges())
AssertionError
```

On version:

``` python
>>> import networkx
>>> print networkx.__version__
1.8.dev_20130123112027
```



Discussion:

Resolving patch:
diff --git a/networkx/readwrite/json_graph/adjacency.py b/networkx/readwrite/json_graph/adjacency.py
--- a/networkx/readwrite/json_graph/adjacency.py
+++ b/networkx/readwrite/json_graph/adjacency.py
@@ -1,4 +1,4 @@
-#    Copyright (C) 2011 by 
+#    Copyright (C) 2011-2013 by
 #    Aric Hagberg <hagberg@lanl.gov>
 #    Dan Schult <dschult@colgate.edu>
 #    Pieter Swart <swart@lanl.gov>
@@ -8,7 +8,7 @@
 from itertools import count,repeat
 import json
 import networkx as nx
-__author__ = """Aric Hagberg (hagberg@lanl.gov))"""
+__author__ = """Aric Hagberg <aric.hagberg@gmail.com>"""
 __all__ = ['adjacency_data', 'adjacency_graph']
 
 def adjacency_data(G):
@@ -18,7 +18,7 @@ def adjacency_data(G):
     Parameters
     ----------
     G : NetworkX graph
-    
+
     Returns
     -------
     data : dict
@@ -34,47 +34,54 @@ def adjacency_data(G):
 
     >>> import json
     >>> s = json.dumps(data)
-    
+
     Notes
     -----
-    Graph, node, and link attributes are stored in this format but keys 
-    for attributes must be strings if you want to serialize with JSON.
+    Graph, node, and link attributes will be written when using this format
+    but attribute keys must be strings if you want to serialize the resulting
+    data with JSON.
 
     See Also
     --------
     adjacency_graph, node_link_data, tree_data
     """
+    multigraph = G.is_multigraph()
     data = {}
     data['directed'] = G.is_directed()
-    data['multigraph'] = G.is_multigraph()
+    data['multigraph'] = multigraph
     data['graph'] = list(G.graph.items())
     data['nodes'] = []
     data['adjacency'] = []
     for n,nbrdict in G.adjacency_iter():
         data['nodes'].append(dict(id=n, **G.node[n]))
         adj = []
-        for nbr,d in nbrdict.items():
-            adj.append(dict(id=nbr, **d))
+        if multigraph:
+            for nbr,key in nbrdict.items():
+                for k,d in key.items():
+                    adj.append(dict(id=nbr, key=k, **d))
+        else:
+            for nbr,d in nbrdict.items():
+                adj.append(dict(id=nbr, **d))
         data['adjacency'].append(adj)
     return data
 
 def adjacency_graph(data, directed=False, multigraph=True):
-    """Return graph from adjacency data format. 
+    """Return graph from adjacency data format.
 
     Parameters
     ----------
     data : dict
         Adjacency list formatted graph data
-    
+
     Returns
     -------
     G : NetworkX graph
        A NetworkX graph object
 
-    directed : bool        
+    directed : bool
         If True, and direction not specified in data, return a directed graph.
 
-    multigraph : bool        
+    multigraph : bool
         If True, and multigraph not specified in data, return a multigraph.
 
     Examples
@@ -99,13 +106,18 @@ def adjacency_graph(data, directed=False, multigraph=True):
     graph.graph = dict(data.get('graph',[]))
     mapping=[]
     for d in data['nodes']:
-        node = d.pop('id')
+        node_data = d.copy()
+        node = node_data.pop('id')
         mapping.append(node)
-        graph.add_node(node, attr_dict=d)
+        graph.add_node(node, attr_dict=node_data)
     for i,d in enumerate(data['adjacency']):
         source = mapping[i]
         for tdata in d:
-            target=tdata.pop('id')
-            graph.add_edge(source,target,attr_dict=tdata)
+            target_data = tdata.copy()
+            target = target_data.pop('id')
+            key = target_data.pop('key', None)
+            if not multigraph or key is None:
+                graph.add_edge(source,target,attr_dict=tdata)
+            else:
+                graph.add_edge(source,target,key=key, attr_dict=tdata)
     return graph
-
diff --git a/networkx/readwrite/json_graph/node_link.py b/networkx/readwrite/json_graph/node_link.py
--- a/networkx/readwrite/json_graph/node_link.py
+++ b/networkx/readwrite/json_graph/node_link.py
@@ -1,4 +1,4 @@
-#    Copyright (C) 2011 by 
+#    Copyright (C) 2011-2013 by
 #    Aric Hagberg <hagberg@lanl.gov>
 #    Dan Schult <dschult@colgate.edu>
 #    Pieter Swart <swart@lanl.gov>
@@ -7,17 +7,17 @@
 from itertools import count,repeat
 import json
 import networkx as nx
-__author__ = """Aric Hagberg (hagberg@lanl.gov))"""
+__author__ = """Aric Hagberg <hagberg@lanl.gov>"""
 __all__ = ['node_link_data', 'node_link_graph']
 
-def node_link_data(G): 
+def node_link_data(G):
     """Return data in node-link format that is suitable for JSON serialization
     and use in Javascript documents.
 
     Parameters
     ----------
     G : NetworkX graph
-    
+
     Returns
     -------
     data : dict
@@ -33,38 +33,45 @@ def node_link_data(G):
 
     >>> import json
     >>> s = json.dumps(data)
-    
+
     Notes
     -----
-    Graph, node, and link attributes are stored in this format but keys 
+    Graph, node, and link attributes are stored in this format but keys
     for attributes must be strings if you want to serialize with JSON.
 
     See Also
     --------
     node_link_graph, adjacency_data, tree_data
     """
+    multigraph = G.is_multigraph()
     mapping = dict(zip(G,count()))
     data = {}
     data['directed'] = G.is_directed()
-    data['multigraph'] = G.is_multigraph()
+    data['multigraph'] = multigraph
     data['graph'] = list(G.graph.items())
     data['nodes'] = [ dict(id=n, **G.node[n]) for n in G ]
-    data['links'] = [ dict(source=mapping[u], target=mapping[v], **d)
-                      for u,v,d in G.edges(data=True) ]
+    if multigraph:
+        data['links'] = [ dict(source=mapping[u], target=mapping[v], key=k, **d)
+                          for u,v,k,d in G.edges(keys=True, data=True) ]
+    else:
+        data['links'] = [ dict(source=mapping[u], target=mapping[v], **d)
+                          for u,v,d in G.edges(data=True) ]
+
     return data
 
+
 def node_link_graph(data, directed=False, multigraph=True):
-    """Return graph from node-link data format. 
+    """Return graph from node-link data format.
 
     Parameters
     ----------
     data : dict
         node-link formatted graph data
-    
-    directed : bool        
+
+    directed : bool
         If True, and direction not specified in data, return a directed graph.
 
-    multigraph : bool        
+    multigraph : bool
         If True, and multigraph not specified in data, return a multigraph.
 
     Returns
@@ -100,10 +107,10 @@ def node_link_graph(data, directed=False, multigraph=True):
         nodedata = dict((str(k),v) for k,v in d.items() if k!='id')
         graph.add_node(node, **nodedata)
     for d in data['links']:
-        source = d.pop('source')
-        target = d.pop('target')
-        edgedata = dict((str(k),v) for k,v in d.items() 
+        link_data = d.copy()
+        source = link_data.pop('source')
+        target = link_data.pop('target')
+        edgedata = dict((str(k),v) for k,v in d.items()
                         if k!='source' and k!='target')
         graph.add_edge(mapping[source],mapping[target],**edgedata)
     return graph
-


Test patch:
diff --git a/networkx/readwrite/json_graph/tests/test_adjacency.py b/networkx/readwrite/json_graph/tests/test_adjacency.py
--- a/networkx/readwrite/json_graph/tests/test_adjacency.py
+++ b/networkx/readwrite/json_graph/tests/test_adjacency.py
@@ -28,14 +28,25 @@ def test_graph_attributes(self):
         assert_equal(H.graph[1],'one')
         assert_equal(H.node[1]['color'],'red')
         assert_equal(H[1][2]['width'],7)
-        
+
     def test_digraph(self):
         G = nx.DiGraph()
+        G.add_path([1,2,3])
         H = adjacency_graph(adjacency_data(G))
         assert_true(H.is_directed())
+        nx.is_isomorphic(G,H)
 
     def test_multidigraph(self):
         G = nx.MultiDiGraph()
+        G.add_path([1,2,3])
         H = adjacency_graph(adjacency_data(G))
         assert_true(H.is_directed())
         assert_true(H.is_multigraph())
+
+    def test_multigraph(self):
+        G = nx.MultiGraph()
+        G.add_edge(1,2,key='first')
+        G.add_edge(1,2,key='second',color='blue')
+        H = adjacency_graph(adjacency_data(G))
+        nx.is_isomorphic(G,H)
+        assert_equal(H[1][2]['second']['color'],'blue')
diff --git a/networkx/readwrite/json_graph/tests/test_node_link.py b/networkx/readwrite/json_graph/tests/test_node_link.py
--- a/networkx/readwrite/json_graph/tests/test_node_link.py
+++ b/networkx/readwrite/json_graph/tests/test_node_link.py
@@ -34,8 +34,11 @@ def test_digraph(self):
         H = node_link_graph(node_link_data(G))
         assert_true(H.is_directed())
 
-    def test_multidigraph(self):
-        G = nx.MultiDiGraph()
+
+    def test_multigraph(self):
+        G = nx.MultiGraph()
+        G.add_edge(1,2,key='first')
+        G.add_edge(1,2,key='second',color='blue')
         H = node_link_graph(node_link_data(G))
-        assert_true(H.is_directed())
-        assert_true(H.is_multigraph())
+        nx.is_isomorphic(G,H)
+        assert_equal(H[1][2]['second']['color'],'blue')