Adjacency List to JSON graph with Postgres

Here's a solution using PLV8 for your schema.

First, build a materialized path using PLSQL function and recursive CTEs.

CREATE OR REPLACE FUNCTION get_children(tag_id integer)
RETURNS json AS $$
DECLARE
result json;
BEGIN
SELECT array_to_json(array_agg(row_to_json(t))) INTO result
    FROM (
WITH RECURSIVE tree AS (
  SELECT id, name, ARRAY[]::INTEGER[] AS ancestors
  FROM tags WHERE parent_id IS NULL

  UNION ALL

  SELECT tags.id, tags.name, tree.ancestors || tags.parent_id
  FROM tags, tree
  WHERE tags.parent_id = tree.id
) SELECT id, name, ARRAY[]::INTEGER[] AS children FROM tree WHERE $1 = tree.ancestors[array_upper(tree.ancestors,1)]
) t;
RETURN result;
END;
$$ LANGUAGE plpgsql;

Then, build the tree from the output of the above function.

CREATE OR REPLACE FUNCTION get_tree(data json) RETURNS json AS $$

var root = [];

for(var i in data) {
  build_tree(data[i]['id'], data[i]['name'], data[i]['children']);
}

function build_tree(id, name, children) {
  var exists = getObject(root, id);
  if(exists) {
       exists['children'] = children;
  }
  else {
    root.push({'id': id, 'name': name, 'children': children});
  }
}


function getObject(theObject, id) {
    var result = null;
    if(theObject instanceof Array) {
        for(var i = 0; i < theObject.length; i++) {
            result = getObject(theObject[i], id);
            if (result) {
                break;
            }   
        }
    }
    else
    {
        for(var prop in theObject) {
            if(prop == 'id') {
                if(theObject[prop] === id) {
                    return theObject;
                }
            }
            if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
                result = getObject(theObject[prop], id);
                if (result) {
                    break;
                }
            } 
        }
    }
    return result;
}

    return JSON.stringify(root);
$$ LANGUAGE plv8 IMMUTABLE STRICT;

This will yield the required JSON mentioned in your question. Hope that helps.

I've written a detailed post/breakdown of how this solution works here.


i was finding same solution and may be this example could be useful for anyone

tested on Postgres 10 with table with same structure

table with columns: id, name and pid as parent_id


create or replace function get_c_tree(p_parent int8) returns setof jsonb as $$

  select
    case 
      when count(x) > 0 then jsonb_build_object('id', c.id, 'name', c.name,  'children', jsonb_agg(f.x))
      else jsonb_build_object('id', c.id, 'name', c.name, 'children', null)
    end
  from company c left join get_c_tree(c.id) as f(x) on true
  where c.pid = p_parent or (p_parent is null and c.pid is null)
  group by c.id, c.name;

$$ language sql;


select jsonb_agg(get_c_tree) from get_c_tree(null::int8);

Try PL/Python and networkx.

Admittedly, using the following doesn't yield JSON in exactly the requested format, but the information seems to be all there and, if PL/Python is acceptable, this might be adapted into a complete answer.

CREATE OR REPLACE FUNCTION get_adjacency_data(
    names text[],
    ids integer[],
    parent_ids integer[])
  RETURNS jsonb AS
$BODY$

    pairs = zip(ids, parent_ids)

    import networkx as nx
    import json
    from networkx.readwrite import json_graph

    name_dict = dict(zip(ids, names))

    G=nx.DiGraph()
    G.add_nodes_from(ids)
    nx.set_node_attributes(G, 'name', name_dict)
    G.add_edges_from(pairs)
    return json.dumps(json_graph.adjacency_data(G))

$BODY$ LANGUAGE plpythonu;

WITH raw_data AS (
    SELECT array_agg(name) AS names,
        array_agg(parent_id) AS parent_ids,
        array_agg(id) AS ids
    FROM tags
    WHERE parent_id IS NOT NULL)
SELECT get_adjacency_data(names, parent_ids, ids)
FROM raw_data;