Repository: mathesar-foundation/mathesar

Issue:
`Distinct list` results in pills of characters when applied on `JSON_list` columns.
## Description
`Distinct List` results in pills of character when applied on `JSON_list` columns.

## Expected behavior
Each pill should contain a single list as `JSON_list` or `string`. The cell should contain multiple such pills.

## Screenshot

<img width="960" alt="image" src="https://github.com/centerofci/mathesar/assets/64671908/aa3cbd0c-f85b-4d9c-9030-0a23e71f5bbe">



Discussion:

Resolving patch:
diff --git a/conftest.py b/conftest.py
--- a/conftest.py
+++ b/conftest.py
@@ -261,6 +261,7 @@ def _get_connection_string(username, password, hostname, database):
 LIBRARY_SQL = os.path.join(RESOURCES, "library_without_checkouts.sql")
 LIBRARY_CHECKOUTS_SQL = os.path.join(RESOURCES, "library_add_checkouts.sql")
 FRAUDULENT_PAYMENTS_SQL = os.path.join(RESOURCES, "fraudulent_payments.sql")
+PLAYER_PROFILES_SQL = os.path.join(RESOURCES, "player_profiles.sql")
 
 
 @pytest.fixture
@@ -349,3 +350,20 @@ def payments_db_table(engine_with_fraudulent_payment):
     metadata = MetaData(bind=engine)
     table = Table("Payments", metadata, schema=schema, autoload_with=engine)
     return table
+
+
+@pytest.fixture
+def engine_with_player_profiles(engine_with_schema):
+    engine, schema = engine_with_schema
+    with engine.begin() as conn, open(PLAYER_PROFILES_SQL) as f:
+        conn.execute(text(f"SET search_path={schema}"))
+        conn.execute(text(f.read()))
+    yield engine, schema
+
+
+@pytest.fixture
+def players_db_table(engine_with_player_profiles):
+    engine, schema = engine_with_player_profiles
+    metadata = MetaData(bind=engine)
+    table = Table("Players", metadata, schema=schema, autoload_with=engine)
+    return table
diff --git a/db/types/custom/json_array.py b/db/types/custom/json_array.py
--- a/db/types/custom/json_array.py
+++ b/db/types/custom/json_array.py
@@ -4,14 +4,16 @@
 from sqlalchemy.ext.compiler import compiles
 
 from db.types.base import MathesarCustomType
+from db.types.custom.underlying_type import HasUnderlyingType
 from db.utils import ignore_duplicate_wrapper
 
 DB_TYPE = MathesarCustomType.MATHESAR_JSON_ARRAY.id
 
 
-class MathesarJsonArray(TypeDecorator):
+class MathesarJsonArray(TypeDecorator, HasUnderlyingType):
     impl = SA_JSONB
     cache_ok = True
+    underlying_type = impl
 
     def get_col_spec(self, **_):
         return DB_TYPE.upper()


Test patch:
diff --git a/db/tests/resources/player_profiles.sql b/db/tests/resources/player_profiles.sql
new file mode 100644
--- /dev/null
+++ b/db/tests/resources/player_profiles.sql
@@ -0,0 +1,107 @@
+/*
+This test schema is for mathesar_types.mathesar_json_array.
+This solely tests the result of distinct_list_aggregation 
+on SQL and JSON arrays.
+*/
+
+CREATE TABLE "Players" (
+    id integer NOT NULL,
+    "player" VARCHAR(255) NOT NULL,
+    "country" VARCHAR(100) NOT NULL,
+    "ballon_dor" mathesar_types.mathesar_json_array,
+    "titles" mathesar_types.mathesar_json_array
+);
+
+CREATE SEQUENCE "Players_id_seq"
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE "Players_id_seq" OWNED BY "Players".id;
+
+ALTER TABLE ONLY "Players" 
+  ALTER COLUMN id SET DEFAULT nextval('"Players_id_seq"'::regclass);
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Lionel Messi', 'Argentina', '[2009, 2011, 2012, 2013, 2016, 2019, 2021]', '[{"world_cup": 1, "ucl": 4}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Cristiano Ronaldo', 'Portugal', '[2008, 2010, 2014, 2015, 2017]', '[{"world_cup": 0, "ucl": 5}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Michel Platini', 'France', '[1983, 1984, 1985]', '[{"world_cup": 0, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Johan Cruyff', 'Netherlands', '[1971, 1973, 1974]', '[{"world_cup": 0, "ucl": 3}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Marco van Basten', 'Netherlands', '[1988, 1992, 1993]', '[{"world_cup": 1, "ucl": 3}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Franz Beckenbauer', 'Germany', '[1972, 1976]', '[{"world_cup": 1, "ucl": 3}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Alfredo Di Stefano', 'Argentina', '[1957, 1959, 1960]', '[{"world_cup": 0, "ucl": 5}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Zinedine Zidane', 'France', '[1998]', '[{"world_cup": 1, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Ronaldo Nazario', 'Brazil', '[1997, 2002]', '[{"world_cup": 2, "ucl": 3}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Ronaldinho', 'Brazil', '[2005]', '[{"world_cup": 1, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Andrea Pirlo', 'Italy', '[2006]', '[{"world_cup": 1, "ucl": 2}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Gerd Muller', 'Germany', '[1970]', '[{"world_cup": 1, "ucl": 3}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Lev Yashin', 'Soviet Union', '[1963]', '[{"world_cup": 0, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Ferenc Puskas', 'Hungary', '[1959]', '[{"world_cup": 0, "ucl": 3}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('George Weah', 'Liberia', '[1995]', '[{"world_cup": 0, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Rivaldo', 'Brazil', '[1999]', '[{"world_cup": 1, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Fabio Cannavaro', 'Italy', '[2006]', '[{"world_cup": 1, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Lothar Matthaus', 'Germany', '[1990]', '[{"world_cup": 1, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Garrincha', 'Brazil', '[1962]', '[{"world_cup": 2, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Kaka', 'Brazil', '[2007]', '[{"world_cup": 1, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Luigi Riva', 'Italy', '[1969]', '[{"world_cup": 0, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Roberto Baggio', 'Italy', '[1993]', '[{"world_cup": 0, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Bobby Charlton', 'England', '[1966]', '[{"world_cup": 1, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Raymond Kopa', 'France', '[1958]', '[{"world_cup": 0, "ucl": 0}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Luka Modric', 'Croatia', '[2018]', '[{"world_cup": 0, "ucl": 4}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Andriy Shevchenko', 'Ukraine', '[2004]', '[{"world_cup": 0, "ucl": 1}]');
+
+INSERT INTO "Players" ("player", "country", "ballon_dor", "titles")
+VALUES ('Oleg Blokhin', 'Soviet Union', '[1975]', '[{"world_cup": 0, "ucl": 0}]');
diff --git a/mathesar/tests/api/conftest.py b/mathesar/tests/api/conftest.py
--- a/mathesar/tests/api/conftest.py
+++ b/mathesar/tests/api/conftest.py
@@ -303,6 +303,12 @@ def payments_ma_table(db_table_to_dj_table, payments_db_table):
     return db_table_to_dj_table(payments_db_table)
 
 
+@pytest.fixture
+def players_ma_table(db_table_to_dj_table, players_db_table):
+    reset_reflection()
+    return db_table_to_dj_table(players_db_table)
+
+
 @pytest.fixture
 def table_with_unknown_types(create_schema, get_uid, engine):
     prefix = "unknown_types"
diff --git a/mathesar/tests/api/query/test_aggregation_functions.py b/mathesar/tests/api/query/test_aggregation_functions.py
--- a/mathesar/tests/api/query/test_aggregation_functions.py
+++ b/mathesar/tests/api/query/test_aggregation_functions.py
@@ -966,6 +966,394 @@ def test_percentage_true_aggregation(payments_ma_table, get_uid, client):
     assert sorted(actual_records, key=lambda x: x['Payment Mode']) == expect_records
 
 
+def test_list_aggregation_mathesar_json_array(players_ma_table, get_uid, client):
+    _ = players_ma_table
+    players = {
+        t["name"]: t for t in client.get("/api/db/v0/tables/").json()["results"]
+    }["Players"]
+    columns = {
+        c["name"]: c for c in players["columns"]
+    }
+    request_data = {
+        "name": get_uid(),
+        "base_table": players["id"],
+        "initial_columns": [
+            {"id": columns["ballon_dor"]["id"], "alias": "ballon_dor"},
+            {"id": columns["country"]["id"], "alias": "country"},
+            {"id": columns["titles"]["id"], "alias": "titles"},
+        ],
+        "display_names": {
+            "ballon_dor": "ballon_dor",
+            "country": "country",
+            "titles": "titles",
+        },
+        "display_options": {
+            "country": {
+                display_option_origin: "country",
+            },
+            "ballon_dor": {
+                display_option_origin: "ballon_dor",
+            },
+            "titles": {
+                display_option_origin: "titles",
+            },
+        },
+        "transformations": [
+            {
+                "spec": {
+                    "grouping_expressions": [
+                        {
+                            "input_alias": "country",
+                            "output_alias": "country",
+                        }
+                    ],
+                    "aggregation_expressions": [
+                        {
+                            "input_alias": "ballon_dor",
+                            "output_alias": "ballon_dor",
+                            "function": "distinct_aggregate_to_array",
+                        },
+                        {
+                            "input_alias": "titles",
+                            "output_alias": "titles",
+                            "function": "distinct_aggregate_to_array",
+                        }
+                    ]
+                },
+                "type": "summarize",
+            }
+        ]
+    }
+    response = client.post('/api/db/v0/queries/', data=request_data)
+    assert response.status_code == 201
+    query_id = response.json()['id']
+    expect_records = [
+        {
+            "country": "Argentina",
+            "ballon_dor": [
+                [
+                    1957,
+                    1959,
+                    1960
+                ],
+                [
+                    2009,
+                    2011,
+                    2012,
+                    2013,
+                    2016,
+                    2019,
+                    2021
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 4,
+                        "world_cup": 1
+                    }
+                ],
+                [
+                    {
+                        "ucl": 5,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Brazil",
+            "ballon_dor": [
+                [
+                    1962
+                ],
+                [
+                    1999
+                ],
+                [
+                    2005
+                ],
+                [
+                    2007
+                ],
+                [
+                    1997,
+                    2002
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 2
+                    }
+                ],
+                [
+                    {
+                        "ucl": 1,
+                        "world_cup": 1
+                    }
+                ],
+                [
+                    {
+                        "ucl": 3,
+                        "world_cup": 2
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Croatia",
+            "ballon_dor": [
+                [
+                    2018
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 4,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "England",
+            "ballon_dor": [
+                [
+                    1966
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 1,
+                        "world_cup": 1
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "France",
+            "ballon_dor": [
+                [
+                    1958
+                ],
+                [
+                    1998
+                ],
+                [
+                    1983,
+                    1984,
+                    1985
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 0
+                    }
+                ],
+                [
+                    {
+                        "ucl": 1,
+                        "world_cup": 0
+                    }
+                ],
+                [
+                    {
+                        "ucl": 1,
+                        "world_cup": 1
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Germany",
+            "ballon_dor": [
+                [
+                    1970
+                ],
+                [
+                    1990
+                ],
+                [
+                    1972,
+                    1976
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 1
+                    }
+                ],
+                [
+                    {
+                        "ucl": 3,
+                        "world_cup": 1
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Hungary",
+            "ballon_dor": [
+                [
+                    1959
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 3,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Italy",
+            "ballon_dor": [
+                [
+                    1969
+                ],
+                [
+                    1993
+                ],
+                [
+                    2006
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 0
+                    }
+                ],
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 1
+                    }
+                ],
+                [
+                    {
+                        "ucl": 2,
+                        "world_cup": 1
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Liberia",
+            "ballon_dor": [
+                [
+                    1995
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Netherlands",
+            "ballon_dor": [
+                [
+                    1971,
+                    1973,
+                    1974
+                ],
+                [
+                    1988,
+                    1992,
+                    1993
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 3,
+                        "world_cup": 0
+                    }
+                ],
+                [
+                    {
+                        "ucl": 3,
+                        "world_cup": 1
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Portugal",
+            "ballon_dor": [
+                [
+                    2008,
+                    2010,
+                    2014,
+                    2015,
+                    2017
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 5,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Soviet Union",
+            "ballon_dor": [
+                [
+                    1963
+                ],
+                [
+                    1975
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 0,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        },
+        {
+            "country": "Ukraine",
+            "ballon_dor": [
+                [
+                    2004
+                ]
+            ],
+            "titles": [
+                [
+                    {
+                        "ucl": 1,
+                        "world_cup": 0
+                    }
+                ]
+            ]
+        }
+    ]
+    actual_records = client.get(f'/api/db/v0/queries/{query_id}/records/').json()['results']
+    assert sorted(actual_records, key=lambda x: x['country']) == expect_records
+
+
 def test_Mathesar_money_distinct_list_aggregation(library_ma_tables, get_uid, client):
     _ = library_ma_tables
     items = {