I'm gonna assume the name's can vary cause without it it's no challenge.
Usins the eval function:
CREATE OR REPLACE FUNCTION eval(expression text) RETURNS text
AS
$body$
DECLARE
result text;
BEGIN
execute expression INTO RESULT;
RETURN result;
END;
$body$
language plpgsql
We can create a dynamic crosstab:
SELECT eval('SELECT row_to_json(q)
FROM (SELECT '||(SELECT string_agg(DISTINCT '"'||name||'"',',')
FROM roles)||'
FROM crosstab(''SELECT 1,name,array_agg(person)::text[]
FROM roles
GROUP BY name;
'') as ct(row_name int,'||(SELECT string_agg(DISTINCT '"'||name||'" text[]',',')
FROM roles)||')
)as q');
EDIT
Less fancy aproach:
SELECT '{'||string_agg(temp,',')||'}'
FROM
(
SELECT '"'||name||'": ['||(string_agg(person,',')::text)||']' as temp FROM roles GROUP BY name
) as q