Repository: tobymao/sqlglot

Issue:
parse_one does not work with json_object 
sqlglot version 11.4.3

The following sql query fails to be parsed by sqlglot but I can run it in trino

```
WITH
v AS (SELECT * FROM companies),
v1 AS (SELECT Name, Notes, Active, coalesce(element_at(map_from_entries(ARRAY[(1, JSON_ARRAY(JSON_OBJECT('id': '5', 'value': 'AutoMotive', 'color': 'blue'))), (2, JSON_ARRAY(JSON_OBJECT('id': '3', 'value': 'Retail', 'color': 'dark-orange'), JSON_OBJECT('id': '4', 'value': 'Online', 'color': 'light-red'))), (1000000072, JSON_ARRAY(JSON_OBJECT('id': '3', 'value': 'Retail', 'color': 'dark-orange'))), (1000000054, JSON_ARRAY(JSON_OBJECT('id': '6', 'value': 'FMCG', 'color': 'blue')))]), id), Tags) Tags, date, id FROM v)
SELECT * from v1;
```

I have set the dialect to Trino

The error is,
```
Expecting ).(line: 3, column: 110)
```

Could you help me with this?


Discussion:

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -3611,6 +3611,21 @@ class Initcap(Func):
     pass
 
 
+class JSONKeyValue(Expression):
+    arg_types = {"this": True, "expression": True}
+
+
+class JSONObject(Func):
+    arg_types = {
+        "expressions": False,
+        "null_handling": False,
+        "unique_keys": False,
+        "return_type": False,
+        "format_json": False,
+        "encoding": False,
+    }
+
+
 class JSONBContains(Binary):
     _sql_names = ["JSONB_CONTAINS"]
 
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -1590,6 +1590,25 @@ def if_sql(self, expression: exp.If) -> str:
             exp.Case(ifs=[expression.copy()], default=expression.args.get("false"))
         )
 
+    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:
+        return f"{self.sql(expression, 'this')}: {self.sql(expression, 'expression')}"
+
+    def jsonobject_sql(self, expression: exp.JSONObject) -> str:
+        expressions = self.expressions(expression)
+        null_handling = expression.args.get("null_handling")
+        null_handling = f" {null_handling}" if null_handling else ""
+        unique_keys = expression.args.get("unique_keys")
+        if unique_keys is not None:
+            unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS"
+        else:
+            unique_keys = ""
+        return_type = self.sql(expression, "return_type")
+        return_type = f" RETURNING {return_type}" if return_type else ""
+        format_json = " FORMAT JSON" if expression.args.get("format_json") else ""
+        encoding = self.sql(expression, "encoding")
+        encoding = f" ENCODING {encoding}" if encoding else ""
+        return f"JSON_OBJECT({expressions}{null_handling}{unique_keys}{return_type}{format_json}{encoding})"
+
     def in_sql(self, expression: exp.In) -> str:
         query = expression.args.get("query")
         unnest = expression.args.get("unnest")
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -674,6 +674,7 @@ class Parser(metaclass=_Parser):
         "CAST": lambda self: self._parse_cast(self.STRICT_CAST),
         "CONVERT": lambda self: self._parse_convert(self.STRICT_CAST),
         "EXTRACT": lambda self: self._parse_extract(),
+        "JSON_OBJECT": lambda self: self._parse_json_object(),
         "POSITION": lambda self: self._parse_position(),
         "STRING_AGG": lambda self: self._parse_string_agg(),
         "SUBSTRING": lambda self: self._parse_substring(),
@@ -3337,6 +3338,47 @@ def _parse_convert(self, strict: bool) -> t.Optional[exp.Expression]:
 
         return self.expression(exp.Cast if strict else exp.TryCast, this=this, to=to)
 
+    def _parse_json_key_value(self) -> t.Optional[exp.Expression]:
+        self._match_text_seq("KEY")
+        key = self._parse_field()
+        self._match(TokenType.COLON)
+        self._match_text_seq("VALUE")
+        value = self._parse_field()
+        if not key and not value:
+            return None
+        return self.expression(exp.JSONKeyValue, this=key, expression=value)
+
+    def _parse_json_object(self) -> exp.Expression:
+        expressions = self._parse_csv(self._parse_json_key_value)
+
+        null_handling = None
+        if self._match_text_seq("NULL", "ON", "NULL"):
+            null_handling = "NULL ON NULL"
+        elif self._match_text_seq("ABSENT", "ON", "NULL"):
+            null_handling = "ABSENT ON NULL"
+
+        unique_keys = None
+        if self._match_text_seq("WITH", "UNIQUE"):
+            unique_keys = True
+        elif self._match_text_seq("WITHOUT", "UNIQUE"):
+            unique_keys = False
+
+        self._match_text_seq("KEYS")
+
+        return_type = self._match_text_seq("RETURNING") and self._parse_type()
+        format_json = self._match_text_seq("FORMAT", "JSON")
+        encoding = self._match_text_seq("ENCODING") and self._parse_var()
+
+        return self.expression(
+            exp.JSONObject,
+            expressions=expressions,
+            null_handling=null_handling,
+            unique_keys=unique_keys,
+            return_type=return_type,
+            format_json=format_json,
+            encoding=encoding,
+        )
+
     def _parse_position(self, haystack_first: bool = False) -> exp.Expression:
         args = self._parse_csv(self._parse_bitwise)
 


Test patch:
diff --git a/tests/dialects/test_presto.py b/tests/dialects/test_presto.py
--- a/tests/dialects/test_presto.py
+++ b/tests/dialects/test_presto.py
@@ -369,6 +369,12 @@ def test_presto(self):
         self.validate_identity("START TRANSACTION ISOLATION LEVEL REPEATABLE READ")
         self.validate_identity("APPROX_PERCENTILE(a, b, c, d)")
 
+        self.validate_all(
+            "SELECT JSON_OBJECT(KEY 'key1' VALUE 1, KEY 'key2' VALUE TRUE)",
+            write={
+                "presto": "SELECT JSON_OBJECT('key1': 1, 'key2': TRUE)",
+            },
+        )
         self.validate_all(
             "ARRAY_AGG(x ORDER BY y DESC)",
             write={
diff --git a/tests/fixtures/identity.sql b/tests/fixtures/identity.sql
--- a/tests/fixtures/identity.sql
+++ b/tests/fixtures/identity.sql
@@ -789,3 +789,11 @@ PRAGMA memory_limit = '1GB'
 PRAGMA schema.synchronous = 2
 PRAGMA schema.synchronous = FULL
 PRAGMA schema.memory_limit = '1GB'
+JSON_OBJECT()
+JSON_OBJECT('key1': 1, 'key2': TRUE)
+JSON_OBJECT('id': '5', 'fld1': 'bla', 'fld2': 'bar')
+JSON_OBJECT('x': NULL, 'y': 1 NULL ON NULL)
+JSON_OBJECT('x': NULL, 'y': 1 WITH UNIQUE KEYS)
+JSON_OBJECT('x': NULL, 'y': 1 ABSENT ON NULL WITH UNIQUE KEYS)
+JSON_OBJECT('x': 1 RETURNING VARCHAR(100))
+JSON_OBJECT('x': 1 RETURNING VARBINARY FORMAT JSON ENCODING UTF8)