diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8debb8cbbcc..a072b976161 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13050,30 +13050,72 @@ table2-mapping
-
- While the examples for the functions
- json_populate_record,
- json_populate_recordset,
- json_to_record and
- json_to_recordset use constants, the typical use
- would be to reference a table in the FROM clause
- and use one of its json or jsonb columns
- as an argument to the function. Extracted key values can then be
- referenced in other parts of the query, like WHERE
- clauses and target lists. Extracting multiple values in this
- way can improve performance over extracting them separately with
- per-key operators.
-
+
+ The functions
+ json[b]_populate_record,
+ json[b]_populate_recordset,
+ json[b]_to_record and
+ json[b]_to_recordset
+ operate on a JSON object, or array of objects, and extract the values
+ associated with keys whose names match column names of the output row
+ type.
+ Object fields that do not correspond to any output column name are
+ ignored, and output columns that do not match any object field will be
+ filled with nulls.
+ To convert a JSON value to the SQL type of an output column, the
+ following rules are applied in sequence:
+
+
+
+ A JSON null value is converted to a SQL null in all cases.
+
+
+
+
+ If the output column is of type json
+ or jsonb, the JSON value is just reproduced exactly.
+
+
+
+
+ If the output column is a composite (row) type, and the JSON value is
+ a JSON object, the fields of the object are converted to columns of
+ the output row type by recursive application of these rules.
+
+
+
+
+ Likewise, if the output column is an array type and the JSON value is
+ a JSON array, the elements of the JSON array are converted to elements
+ of the output array by recursive application of these rules.
+
+
+
+
+ Otherwise, if the JSON value is a string literal, the contents of the
+ string are fed to the input conversion function for the column's data
+ type.
+
+
+
+
+ Otherwise, the ordinary text representation of the JSON value is fed
+ to the input conversion function for the column's data type.
+
+
+
+
-
- JSON keys are matched to identical column names in the target
- row type. JSON type coercion for these functions is best
- effort
and may not result in desired values for some types.
- JSON fields that do not appear in the target row type will be
- omitted from the output, and target columns that do not match any
- JSON field will simply be NULL.
-
-
+
+ While the examples for these functions use constants, the typical use
+ would be to reference a table in the FROM clause
+ and use one of its json or jsonb columns
+ as an argument to the function. Extracted key values can then be
+ referenced in other parts of the query, like WHERE
+ clauses and target lists. Extracting multiple values in this
+ way can improve performance over extracting them separately with
+ per-key operators.
+
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 28bdc7243fd..9e7035c71a1 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2803,26 +2803,7 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv)
json = jsv->val.json.str;
Assert(json);
-
- /* already done the hard work in the json case */
- if ((typid == JSONOID || typid == JSONBOID) &&
- jsv->val.json.type == JSON_TOKEN_STRING)
- {
- /*
- * Add quotes around string value (should be already escaped) if
- * converting to json/jsonb.
- */
-
- if (len < 0)
- len = strlen(json);
-
- str = palloc(len + sizeof(char) * 3);
- str[0] = '"';
- memcpy(&str[1], json, len);
- str[len + 1] = '"';
- str[len + 2] = '\0';
- }
- else if (len >= 0)
+ if (len >= 0)
{
/* Need to copy non-null-terminated string */
str = palloc(len + 1 * sizeof(char));
@@ -2830,7 +2811,21 @@ populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv)
str[len] = '\0';
}
else
- str = json; /* null-terminated string */
+ str = json; /* string is already null-terminated */
+
+ /* If converting to json/jsonb, make string into valid JSON literal */
+ if ((typid == JSONOID || typid == JSONBOID) &&
+ jsv->val.json.type == JSON_TOKEN_STRING)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ escape_json(&buf, str);
+ /* free temporary buffer */
+ if (str != json)
+ pfree(str);
+ str = buf.data;
+ }
}
else
{
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index cb935980394..5b8e67784f5 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -2276,6 +2276,42 @@ select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
{{{1},{2},{3}}}
(1 row)
+select * from json_to_record('{"out": {"key": 1}}') as x(out json);
+ out
+------------
+ {"key": 1}
+(1 row)
+
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out json);
+ out
+--------------
+ [{"key": 1}]
+(1 row)
+
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out json);
+ out
+----------------
+ "{\"key\": 1}"
+(1 row)
+
+select * from json_to_record('{"out": {"key": 1}}') as x(out jsonb);
+ out
+------------
+ {"key": 1}
+(1 row)
+
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out jsonb);
+ out
+--------------
+ [{"key": 1}]
+(1 row)
+
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb);
+ out
+----------------
+ "{\"key\": 1}"
+(1 row)
+
-- json_strip_nulls
select json_strip_nulls(null);
json_strip_nulls
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 10183030068..469079c5d8f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2652,6 +2652,42 @@ select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
{{{1},{2},{3}}}
(1 row)
+select * from jsonb_to_record('{"out": {"key": 1}}') as x(out json);
+ out
+------------
+ {"key": 1}
+(1 row)
+
+select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out json);
+ out
+--------------
+ [{"key": 1}]
+(1 row)
+
+select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out json);
+ out
+----------------
+ "{\"key\": 1}"
+(1 row)
+
+select * from jsonb_to_record('{"out": {"key": 1}}') as x(out jsonb);
+ out
+------------
+ {"key": 1}
+(1 row)
+
+select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out jsonb);
+ out
+--------------
+ [{"key": 1}]
+(1 row)
+
+select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb);
+ out
+----------------
+ "{\"key\": 1}"
+(1 row)
+
-- test type info caching in jsonb_populate_record()
CREATE TEMP TABLE jsbpoptest (js jsonb);
INSERT INTO jsbpoptest
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 0f0a32e2a0e..a52beaa27a1 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -742,6 +742,13 @@ select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+select * from json_to_record('{"out": {"key": 1}}') as x(out json);
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out json);
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out json);
+select * from json_to_record('{"out": {"key": 1}}') as x(out jsonb);
+select * from json_to_record('{"out": [{"key": 1}]}') as x(out jsonb);
+select * from json_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb);
+
-- json_strip_nulls
select json_strip_nulls(null);
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index c1a7880792d..ba870872e80 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -709,6 +709,13 @@ select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+select * from jsonb_to_record('{"out": {"key": 1}}') as x(out json);
+select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out json);
+select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out json);
+select * from jsonb_to_record('{"out": {"key": 1}}') as x(out jsonb);
+select * from jsonb_to_record('{"out": [{"key": 1}]}') as x(out jsonb);
+select * from jsonb_to_record('{"out": "{\"key\": 1}"}') as x(out jsonb);
+
-- test type info caching in jsonb_populate_record()
CREATE TEMP TABLE jsbpoptest (js jsonb);