diff --git a/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj b/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj
index f3807fdfe6482692f3fe8fbc43fa7d9b06603ee7..46a9ac4cb8c1f83beea4ddff582563826adc6750 100644
--- a/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj
+++ b/modules/drivers/bigquery-cloud-sdk/src/metabase/driver/bigquery_cloud_sdk/query_processor.clj
@@ -17,7 +17,6 @@
             [metabase.query-processor.error-type :as qp.error-type]
             [metabase.query-processor.store :as qp.store]
             [metabase.query-processor.util.add-alias-info :as add]
-            [metabase.query-processor.util.nest-query :as nest-query]
             [metabase.util :as u]
             [metabase.util.date-2 :as u.date]
             [metabase.util.honeysql-extensions :as hx]
@@ -566,24 +565,6 @@
   [_ t]
   (format "timestamp \"%s %s\"" (u.date/format-sql (t/local-date-time t)) (.getId (t/zone-id t))))
 
-;; In `ORDER BY` and `GROUP BY`, unlike other SQL drivers, BigQuery requires that we refer to Fields using the alias we
-;; gave them in the `SELECT` clause, rather than repeating their definitions.
-;;
-;; See #17536 and #18742
-(defn- rewrite-fields-to-force-using-column-aliases
-  "Rewrite `:field` clauses to force them to use the column alias regardless of where they appear."
-  [form]
-  (mbql.u/replace form
-    [:field id-or-name opts]
-    [:field id-or-name (-> opts
-                           (assoc ::add/source-alias        (::add/desired-alias opts)
-                                  ::add/source-table        ::add/none
-                                  ;; sort of a HACK but this key will tell the SQL QP not to apply casting here either.
-                                  ::nest-query/outer-select true)
-                           ;; don't want to do temporal bucketing or binning inside the order by or breakout either.
-                           ;; That happens inside the `SELECT`
-                           (dissoc :temporal-unit :binning))]))
-
 (defmethod sql.qp/apply-top-level-clause [:bigquery-cloud-sdk :breakout]
   [driver top-level-clause honeysql-form query]
   ;; If stuff in `:fields` still needs to be qualified like `dataset.table.field`, just the stuff in `:group-by` should
@@ -592,7 +573,7 @@
   (let [parent-method (partial (get-method sql.qp/apply-top-level-clause [:sql :breakout])
                                driver top-level-clause honeysql-form)
         qualified     (parent-method query)
-        unqualified   (parent-method (update query :breakout rewrite-fields-to-force-using-column-aliases))]
+        unqualified   (parent-method (update query :breakout sql.qp/rewrite-fields-to-force-using-column-aliases))]
     (merge qualified
            (select-keys unqualified #{:group-by}))))
 
@@ -600,13 +581,13 @@
   [driver clause]
   ((get-method sql.qp/->honeysql [:sql :asc])
    driver
-   (rewrite-fields-to-force-using-column-aliases clause)))
+   (sql.qp/rewrite-fields-to-force-using-column-aliases clause)))
 
 (defmethod sql.qp/->honeysql [:bigquery-cloud-sdk :desc]
   [driver clause]
   ((get-method sql.qp/->honeysql [:sql :desc])
    driver
-   (rewrite-fields-to-force-using-column-aliases clause)))
+   (sql.qp/rewrite-fields-to-force-using-column-aliases clause)))
 
 (defn- reconcile-temporal-types
   "Make sure the temporal types of fields and values in filter clauses line up."
diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj
index db8152353b4ee2eb2a8930d50e29df049f23c710..cbcd3d09e9d6a8d39fc1c1dc06aa305e72b699b6 100644
--- a/src/metabase/driver/postgres.clj
+++ b/src/metabase/driver/postgres.clj
@@ -21,6 +21,7 @@
             [metabase.models.field :as field]
             [metabase.models.secret :as secret]
             [metabase.query-processor.store :as qp.store]
+            [metabase.query-processor.util.add-alias-info :as add]
             [metabase.util :as u]
             [metabase.util.date-2 :as u.date]
             [metabase.util.honeysql-extensions :as hx]
@@ -295,7 +296,7 @@
           (format "(%s#>> ?::text[])::%s " (hformat/to-sql parent-identifier) field-type))))))
 
 (defmethod sql.qp/->honeysql [:postgres :field]
-  [driver [_ id-or-name _opts :as clause]]
+  [driver [_ id-or-name opts :as clause]]
   (let [stored-field (when (integer? id-or-name)
                        (qp.store/field id-or-name))
         parent-method (get-method sql.qp/->honeysql [:sql :field])
@@ -305,12 +306,58 @@
       (= (:database_type stored-field) "money")
       (pg-conversion identifier :numeric)
 
-      (some? nfc-path)
-      (json-query identifier stored-field)
+      (field/json-field? stored-field)
+      (if (::sql.qp/forced-alias opts)
+        (keyword (::add/source-alias opts))
+        (json-query identifier stored-field))
 
       :else
       identifier)))
 
+;; Postgres is not happy with JSON fields which are in group-bys or order-bys
+;; being described twice instead of using the alias.
+;; Therefore, force the alias, but only for JSON fields to avoid ambiguity.
+;; The alias names in JSON fields are unique wrt nfc path"
+(defmethod sql.qp/apply-top-level-clause
+  [:postgres :breakout]
+  [driver clause honeysql-form {breakout-fields :breakout, fields-fields :fields :as query}]
+  (let [stored-field-ids (map second breakout-fields)
+        stored-fields    (map #(when (integer? %) (qp.store/field %)) stored-field-ids)
+        parent-method    (partial (get-method sql.qp/apply-top-level-clause [:sql :breakout])
+                                  driver clause honeysql-form)
+        qualified        (parent-method query)
+        unqualified      (parent-method (update query
+                                                :breakout
+                                                sql.qp/rewrite-fields-to-force-using-column-aliases))]
+    (if (some field/json-field? stored-fields)
+      (merge qualified
+             (select-keys unqualified #{:group-by}))
+      qualified)))
+
+(defn- order-by-is-json-field?
+  [clause]
+  (let [is-aggregation? (= (-> clause (second) (first)) :aggregation)
+        stored-field-id (-> clause (second) (second))
+        stored-field    (when (and (not is-aggregation?) (integer? stored-field-id))
+                          (qp.store/field stored-field-id))]
+    (and
+      (some? stored-field)
+      (field/json-field? stored-field))))
+
+(defmethod sql.qp/->honeysql [:postgres :desc]
+  [driver clause]
+  (let [new-clause (if (order-by-is-json-field? clause)
+                     (sql.qp/rewrite-fields-to-force-using-column-aliases clause)
+                     clause)]
+    ((get-method sql.qp/->honeysql [:sql :desc]) driver new-clause)))
+
+(defmethod sql.qp/->honeysql [:postgres :asc]
+  [driver clause]
+  (let [new-clause (if (order-by-is-json-field? clause)
+                     (sql.qp/rewrite-fields-to-force-using-column-aliases clause)
+                     clause)]
+    ((get-method sql.qp/->honeysql [:sql :asc]) driver new-clause)))
+
 (defmethod unprepare/unprepare-value [:postgres Date]
   [_ value]
   (format "'%s'::timestamp" (u.date/format value)))
diff --git a/src/metabase/driver/sql/query_processor.clj b/src/metabase/driver/sql/query_processor.clj
index 3884f917ed4f83b7b9f12fab67d4ab7279fbc348..67d61fe6f180aa0e68ef084b4d9d1b97d607c7c9 100644
--- a/src/metabase/driver/sql/query_processor.clj
+++ b/src/metabase/driver/sql/query_processor.clj
@@ -589,6 +589,27 @@
       [honeysql-form field-alias]
       honeysql-form)))
 
+;; Certain SQL drivers require that we refer to Fields using the alias we give in the `SELECT` clause in
+;; `ORDER BY` and `GROUP BY` rather than repeating definitions.
+;; BigQuery does this generally, other DB's require this in JSON columns.
+;;
+;; See #17536 and #18742
+
+(defn rewrite-fields-to-force-using-column-aliases
+  "Rewrite `:field` clauses to force them to use the column alias regardless of where they appear."
+  [form]
+  (mbql.u/replace form
+    [:field id-or-name opts]
+    [:field id-or-name (-> opts
+                           (assoc ::add/source-alias        (::add/desired-alias opts)
+                                  ::add/source-table        ::add/none
+                                  ;; sort of a HACK but this key will tell the SQL QP not to apply casting here either.
+                                  ::nest-query/outer-select true
+                                  ;; used to indicate that this is a forced alias
+                                  ::forced-alias            true)
+                           ;; don't want to do temporal bucketing or binning inside the order by or breakout either.
+                           ;; That happens inside the `SELECT`
+                           (dissoc :temporal-unit :binning))]))
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                                Clause Handlers                                                 |
diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj
index d30c5e1d2dadae02be3f3001fabc00b0038b3d57..f99b0ff749925f62cd084b53cd91a11764b5c140 100644
--- a/src/metabase/models/field.clj
+++ b/src/metabase/models/field.clj
@@ -332,6 +332,11 @@
                        table-name))))
         field-name))
 
+(defn json-field?
+  "Return true if field is a JSON field, false if not."
+  [field]
+  (some? (:nfc_path field)))
+
 (defn qualified-name
   "Return a combined qualified name for `field`, e.g. `table_name.parent_field_name.field_name`."
   [field]
diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj
index 67625cca824a6103cdb3c3fb37118caedcf27f8a..2483de7c2b86bbb50abd60208f95b8684bd8b522 100644
--- a/test/metabase/driver/postgres_test.clj
+++ b/test/metabase/driver/postgres_test.clj
@@ -291,6 +291,36 @@
         (is (= ["(boop.bleh#>> ?::text[])::boolean " "{boop,foobar,1234}"]
                (hsql/format (#'postgres/json-query boop-identifier boolean-boop-field))))))))
 
+(deftest json-alias-test
+  (mt/test-driver :postgres
+    (testing "json breakouts and order bys have alias coercion"
+      (drop-if-exists-and-create-db! "json-alias-test")
+      (let [details   (mt/dbdef->connection-details :postgres :db {:database-name "json-alias-test"})
+            spec      (sql-jdbc.conn/connection-details->spec :postgres details)
+            json-part (json/generate-string {:bob :dobbs})
+            insert    (str "CREATE TABLE json_alias_test (json_part JSON NOT NULL);"
+                         (format "INSERT INTO json_alias_test (json_part) VALUES ('%s');" json-part))]
+        (jdbc/with-db-connection [conn (sql-jdbc.conn/connection-details->spec :postgres details)]
+          (jdbc/execute! spec [insert]))
+        (mt/with-temp* [Database [database    {:engine :postgres, :details details}]
+                        Table    [table       {:db_id (u/the-id database) :name "json_alias_test"}]
+                        Field    [field       {:table_id (u/the-id table)
+                                               :nfc_path [:bob
+                                                          "injection' OR 1=1--' AND released = 1"
+                                                          (keyword "injection' OR 1=1--' AND released = 1")],
+                                               :name     "json_alias_test"}]]
+          (let [compile-res (qp/compile
+                              {:database (u/the-id database)
+                               :type     :query
+                               :query    {:source-table (u/the-id table)
+                                          :aggregation  [[:count]]
+                                          :breakout     [[:field (u/the-id field) nil]]}})]
+            (is (= (str "SELECT (\"json_alias_test\".\"bob\"#>> ?::text[])::VARCHAR  "
+                        "AS \"json_alias_test\", count(*) AS \"count\" FROM \"json_alias_test\" "
+                        "GROUP BY \"json_alias_test\" ORDER BY \"json_alias_test\" ASC")
+                   (:query compile-res)))
+            (is (= '("{injection' OR 1=1--' AND released = 1,injection' OR 1=1--' AND released = 1}") (:params compile-res)))))))))
+
 (deftest describe-nested-field-columns-test
   (mt/test-driver :postgres
     (testing "describes json columns and gives types for ones with coherent schemas only"