from itertools import chain
import networkx as nx

__all__ = ["tree_data", "tree_graph"]


# NOTE: Remove attrs from signature in 3.0
def tree_data(G, root, attrs=None, ident="id", children="children"):
    """Returns data in tree format that is suitable for JSON serialization
    and use in Javascript documents.

    Parameters
    ----------
    G : NetworkX graph
       G must be an oriented tree

    root : node
       The root of the tree

    attrs : dict
        A dictionary that contains two keys 'id' and 'children'. The
        corresponding values provide the attribute names for storing
        NetworkX-internal graph data. The values should be unique. Default
        value: :samp:`dict(id='id', children='children')`.

        If some user-defined graph data use these attribute names as data keys,
        they may be silently dropped.

        .. deprecated:: 2.6

           The `attrs` keyword argument is replaced by `ident` and `children`
           and will be removed in networkx 3.0

    ident : string
        Attribute name for storing NetworkX-internal graph data. `ident` must
        have a different value than `children`. The default is 'id'.

    children : string
        Attribute name for storing NetworkX-internal graph data. `children`
        must have a different value than `ident`. The default is 'children'.

    Returns
    -------
    data : dict
       A dictionary with node-link formatted data.

    Raises
    ------
    NetworkXError
        If `children` and `ident` attributes are identical.

    Examples
    --------
    >>> from networkx.readwrite import json_graph
    >>> G = nx.DiGraph([(1, 2)])
    >>> data = json_graph.tree_data(G, root=1)

    To serialize with json

    >>> import json
    >>> s = json.dumps(data)

    Notes
    -----
    Node attributes are stored in this format but keys
    for attributes must be strings if you want to serialize with JSON.

    Graph and edge attributes are not stored.

    See Also
    --------
    tree_graph, node_link_data, adjacency_data
    """
    if G.number_of_nodes() != G.number_of_edges() + 1:
        raise TypeError("G is not a tree.")
    if not G.is_directed():
        raise TypeError("G is not directed.")

    # NOTE: to be removed in 3.0
    if attrs is not None:
        import warnings

        msg = (
            "\nThe `attrs` keyword argument of tree_data is deprecated\n"
            "and will be removed in networkx 3.0.\n"
            "It is replaced with explicit `ident` and `children` "
            "keyword arguments.\n"
            "To make this warning go away and ensure usage is forward\n"
            "compatible, replace `attrs` with `ident` and `children,\n"
            "for example:\n\n"
            "    >>> tree_data(G, root, attrs={'id': 'foo', 'children': 'bar'})\n\n"
            "should instead be written as\n\n"
            "    >>> tree_data(G, root, ident='foo', children='bar')\n\n"
            "The default values of 'id' and 'children' will not change."
        )
        warnings.warn(msg, DeprecationWarning, stacklevel=2)

        ident = attrs["id"]
        children = attrs["children"]

    if ident == children:
        raise nx.NetworkXError("The values for `id` and `children` must be different.")

    def add_children(n, G):
        nbrs = G[n]
        if len(nbrs) == 0:
            return []
        children_ = []
        for child in nbrs:
            d = dict(chain(G.nodes[child].items(), [(ident, child)]))
            c = add_children(child, G)
            if c:
                d[children] = c
            children_.append(d)
        return children_

    data = dict(chain(G.nodes[root].items(), [(ident, root)]))
    data[children] = add_children(root, G)
    return data


def tree_graph(data, attrs=None, ident="id", children="children"):
    """Returns graph from tree data format.

    Parameters
    ----------
    data : dict
        Tree formatted graph data
    attrs : dict
        A dictionary that contains two keys 'id' and 'children'. The
        corresponding values provide the attribute names for storing
        NetworkX-internal graph data. The values should be unique. Default
        value: :samp:`dict(id='id', children='children')`.

        .. deprecated:: 2.6

           The `attrs` keyword argument is replaced by `ident` and `children`
           and will be removed in networkx 3.0

    ident : string
        Attribute name for storing NetworkX-internal graph data. `ident` must
        have a different value than `children`. The default is 'id'.

    children : string
        Attribute name for storing NetworkX-internal graph data. `children`
        must have a different value than `ident`. The default is 'children'.

    Returns
    -------
    G : NetworkX DiGraph

    Examples
    --------
    >>> from networkx.readwrite import json_graph
    >>> G = nx.DiGraph([(1, 2)])
    >>> data = json_graph.tree_data(G, root=1)
    >>> H = json_graph.tree_graph(data)

    See Also
    --------
    tree_data, node_link_data, adjacency_data
    """
    graph = nx.DiGraph()
    if attrs is not None:
        import warnings

        msg = (
            "\nThe `attrs` keyword argument of tree_graph is deprecated\n"
            "and will be removed in networkx 3.0.\n"
            "It is replaced with explicit `ident` and `children` "
            "keyword arguments.\n"
            "To make this warning go away and ensure usage is\n"
            "forward compatible, replace `attrs` with `ident` and `children,\n"
            "for example:\n\n"
            "    >>> tree_graph(data, attrs={'id': 'foo', 'children': 'bar'})\n\n"
            "should instead be written as\n\n"
            "    >>> tree_graph(data, ident='foo', children='bar')\n\n"
            "The default values of 'id' and 'children' will not change."
        )
        warnings.warn(msg, DeprecationWarning, stacklevel=2)

        ident = attrs["id"]
        children = attrs["children"]

    def add_children(parent, children_):
        for data in children_:
            child = data[ident]
            graph.add_edge(parent, child)
            grandchildren = data.get(children, [])
            if grandchildren:
                add_children(child, grandchildren)
            nodedata = {
                str(k): v for k, v in data.items() if k != ident and k != children
            }
            graph.add_node(child, **nodedata)

    root = data[ident]
    children_ = data.get(children, [])
    nodedata = {str(k): v for k, v in data.items() if k != ident and k != children}
    graph.add_node(root, **nodedata)
    add_children(root, children_)
    return graph

networkx/readwrite/json_graph/tree.py
from itertools import chain
import networkx as nx

__all__ = ["tree_data", "tree_graph"]


# NOTE: Remove attrs from signature in 3.0
def tree_data(G, root, attrs=None, ident="id", children="children"):
    """Returns data in tree format that is suitable for JSON serialization
    and use in Javascript documents.

    Parameters
    ----------
    G : NetworkX graph
       G must be an oriented tree

    root : node
       The root of the tree

    attrs : dict
        A dictionary that contains two keys 'id' and 'children'. The
        corresponding values provide the attribute names for storing
        NetworkX-internal graph data. The values should be unique. Default
        value: :samp:`dict(id='id', children='children')`.

        If some user-defined graph data use these attribute names as data keys,
        they may be silently dropped.

        .. deprecated:: 2.6

           The `attrs` keyword argument is replaced by `ident` and `children`
           and will be removed in networkx 3.0

    ident : string
        Attribute name for storing NetworkX-internal graph data. `ident` must
        have a different value than `children`. The default is 'id'.

    children : string
        Attribute name for storing NetworkX-internal graph data. `children`
        must have a different value than `ident`. The default is 'children'.

    Returns
    -------
    data : dict
       A dictionary with node-link formatted data.

    Raises
    ------
    NetworkXError
        If `children` and `ident` attributes are identical.

    Examples
    --------
    >>> from networkx.readwrite import json_graph
    >>> G = nx.DiGraph([(1, 2)])
    >>> data = json_graph.tree_data(G, root=1)

    To serialize with json

    >>> import json
    >>> s = json.dumps(data)

    Notes
    -----
    Node attributes are stored in this format but keys
    for attributes must be strings if you want to serialize with JSON.

    Graph and edge attributes are not stored.

    See Also
    --------
    tree_graph, node_link_data, adjacency_data
    """
    if G.number_of_nodes() != G.number_of_edges() + 1:
        raise TypeError("G is not a tree.")
    if not G.is_directed():
        raise TypeError("G is not directed.")
    if not nx.is_weakly_connected(G):
        raise TypeError("G is not weakly connected.")

    # NOTE: to be removed in 3.0
    if attrs is not None:
        import warnings

        msg = (
            "\nThe `attrs` keyword argument of tree_data is deprecated\n"
            "and will be removed in networkx 3.0.\n"
            "It is replaced with explicit `ident` and `children` "
            "keyword arguments.\n"
            "To make this warning go away and ensure usage is forward\n"
            "compatible, replace `attrs` with `ident` and `children,\n"
            "for example:\n\n"
            "    >>> tree_data(G, root, attrs={'id': 'foo', 'children': 'bar'})\n\n"
            "should instead be written as\n\n"
            "    >>> tree_data(G, root, ident='foo', children='bar')\n\n"
            "The default values of 'id' and 'children' will not change."
        )
        warnings.warn(msg, DeprecationWarning, stacklevel=2)

        ident = attrs["id"]
        children = attrs["children"]

    if ident == children:
        raise nx.NetworkXError("The values for `id` and `children` must be different.")

    def add_children(n, G):
        nbrs = G[n]
        if len(nbrs) == 0:
            return []
        children_ = []
        for child in nbrs:
            d = dict(chain(G.nodes[child].items(), [(ident, child)]))
            c = add_children(child, G)
            if c:
                d[children] = c
            children_.append(d)
        return children_

    data = dict(chain(G.nodes[root].items(), [(ident, root)]))
    data[children] = add_children(root, G)
    return data


def tree_graph(data, attrs=None, ident="id", children="children"):
    """Returns graph from tree data format.

    Parameters
    ----------
    data : dict
        Tree formatted graph data
    attrs : dict
        A dictionary that contains two keys 'id' and 'children'. The
        corresponding values provide the attribute names for storing
        NetworkX-internal graph data. The values should be unique. Default
        value: :samp:`dict(id='id', children='children')`.

        .. deprecated:: 2.6

           The `attrs` keyword argument is replaced by `ident` and `children`
           and will be removed in networkx 3.0

    ident : string
        Attribute name for storing NetworkX-internal graph data. `ident` must
        have a different value than `children`. The default is 'id'.

    children : string
        Attribute name for storing NetworkX-internal graph data. `children`
        must have a different value than `ident`. The default is 'children'.

    Returns
    -------
    G : NetworkX DiGraph

    Examples
    --------
    >>> from networkx.readwrite import json_graph
    >>> G = nx.DiGraph([(1, 2)])
    >>> data = json_graph.tree_data(G, root=1)
    >>> H = json_graph.tree_graph(data)

    See Also
    --------
    tree_data, node_link_data, adjacency_data
    """
    graph = nx.DiGraph()
    if attrs is not None:
        import warnings

        msg = (
            "\nThe `attrs` keyword argument of tree_graph is deprecated\n"
            "and will be removed in networkx 3.0.\n"
            "It is replaced with explicit `ident` and `children` "
            "keyword arguments.\n"
            "To make this warning go away and ensure usage is\n"
            "forward compatible, replace `attrs` with `ident` and `children,\n"
            "for example:\n\n"
            "    >>> tree_graph(data, attrs={'id': 'foo', 'children': 'bar'})\n\n"
            "should instead be written as\n\n"
            "    >>> tree_graph(data, ident='foo', children='bar')\n\n"
            "The default values of 'id' and 'children' will not change."
        )
        warnings.warn(msg, DeprecationWarning, stacklevel=2)

        ident = attrs["id"]
        children = attrs["children"]

    def add_children(parent, children_):
        for data in children_:
            child = data[ident]
            graph.add_edge(parent, child)
            grandchildren = data.get(children, [])
            if grandchildren:
                add_children(child, grandchildren)
            nodedata = {
                str(k): v for k, v in data.items() if k != ident and k != children
            }
            graph.add_node(child, **nodedata)

    root = data[ident]
    children_ = data.get(children, [])
    nodedata = {str(k): v for k, v in data.items() if k != ident and k != children}
    graph.add_node(root, **nodedata)
    add_children(root, children_)
    return graph

networkx/readwrite/json_graph/tree.py
diff --git a/networkx/readwrite/json_graph/tests/test_tree.py b/networkx/readwrite/json_graph/tests/test_tree.py
index bfbf18163bc..848edd0b1e0 100644
--- a/networkx/readwrite/json_graph/tests/test_tree.py
+++ b/networkx/readwrite/json_graph/tests/test_tree.py
@@ -35,6 +35,11 @@ def test_exceptions():
     with pytest.raises(TypeError, match="is not directed."):
         G = nx.path_graph(3)
         tree_data(G, 0)
+    with pytest.raises(TypeError, match="is not weakly connected."):
+        G = nx.path_graph(3, create_using=nx.DiGraph)
+        G.add_edge(2, 0)
+        G.add_node(3)
+        tree_data(G, 0)
     with pytest.raises(nx.NetworkXError, match="must be different."):
         G = nx.MultiDiGraph()
         G.add_node(0)
diff --git a/networkx/readwrite/json_graph/tree.py b/networkx/readwrite/json_graph/tree.py
index 2de2ca9b6bc..615907eeb08 100644
--- a/networkx/readwrite/json_graph/tree.py
+++ b/networkx/readwrite/json_graph/tree.py
@@ -75,6 +75,8 @@ def tree_data(G, root, attrs=None, ident="id", children="children"):
         raise TypeError("G is not a tree.")
     if not G.is_directed():
         raise TypeError("G is not directed.")
+    if not nx.is_weakly_connected(G):
+        raise TypeError("G is not weakly connected.")
 
     # NOTE: to be removed in 3.0
     if attrs is not None:

`json_graph.tree_data` can cause maximum recursion depth error.
<!-- If you have a general question about NetworkX, please use the discussions tab to create a new discussion -->

<!--- Provide a general summary of the issue in the Title above -->

### Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
Currently the algorithm compares the `n_nodes` with `n_edges` to check if `G` is a tree. https://github.com/networkx/networkx/blob/0cc70051fa0a979b1f1eab4af5b6587a6ebf8334/networkx/readwrite/json_graph/tree.py#L74-L75 
This check can be bypassed with specific inputs and cause a recursion error.

### Expected Behavior
<!--- Tell us what should happen -->
The code should check whether there are cycles with `root` as the source and raise an exception.
Another possible fix would be to check if the graph is not weakly connected.

### Steps to Reproduce
<!--- Provide a minimal example that reproduces the bug -->
```Python3
>>> import networkx as nx
>>> G = nx.DiGraph([(1, 2), (2, 3), (3, 1)])
>>> G.add_node(4)
>>> data = nx.json_graph.tree_data(G, 1)
RecursionError: maximum recursion depth exceeded
```

### Environment
<!--- Please provide details about your local environment -->
Python version: 3.8.10
NetworkX version: 2.7rc1.dev0

