diff --git a/doc/src/sgml/release-9.4.sgml b/doc/src/sgml/release-9.4.sgml index 08307c8f667..3f30c636ba2 100644 --- a/doc/src/sgml/release-9.4.sgml +++ b/doc/src/sgml/release-9.4.sgml @@ -180,6 +180,21 @@ + + + Unicode escapes in JSON + text values are no longer rendered with the backslash escaped. + (Andrew Dunstan) + + + + Previously all backslashes in text values being formed into JSON were + escaped. Now a backslash followed by "u" and four hexadecimal digits is + not escaped, as this is a legal sequence in a JSON string value, and + escaping the backslash led to some perverse results. + + + Rename EXPLAIN diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 2462111ecb3..8ca1ede83fb 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -2315,7 +2315,26 @@ escape_json(StringInfo buf, const char *str) appendStringInfoString(buf, "\\\""); break; case '\\': - appendStringInfoString(buf, "\\\\"); + /* + * Unicode escapes are passed through as is. There is no + * requirement that they denote a valid character in the + * server encoding - indeed that is a big part of their + * usefulness. + * + * All we require is that they consist of \uXXXX where + * the Xs are hexadecimal digits. It is the responsibility + * of the caller of, say, to_json() to make sure that the + * unicode escape is valid. + * + * In the case of a jsonb string value being escaped, the + * only unicode escape that should be present is \u0000, + * all the other unicode escapes will have been resolved. + */ + if (p[1] == 'u' && isxdigit(p[2]) && isxdigit(p[3]) + && isxdigit(p[4]) && isxdigit(p[5])) + appendStringInfoCharMacro(buf, *p); + else + appendStringInfoString(buf, "\\\\"); break; default: if ((unsigned char) *p < ' ') diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index c4dc8b0e3cb..43341aa9bb5 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -426,6 +426,20 @@ select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); (1 row) COMMIT; +-- unicode escape - backslash is not escaped +select to_json(text '\uabcd'); + to_json +---------- + "\uabcd" +(1 row) + +-- any other backslash is escaped +select to_json(text '\abcd'); + to_json +---------- + "\\abcd" +(1 row) + --json_agg SELECT json_agg(q) FROM ( SELECT $$a$$ || x AS b, y AS c, diff --git a/src/test/regress/expected/json_1.out b/src/test/regress/expected/json_1.out index 629e98e6c5f..953324637d8 100644 --- a/src/test/regress/expected/json_1.out +++ b/src/test/regress/expected/json_1.out @@ -426,6 +426,20 @@ select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); (1 row) COMMIT; +-- unicode escape - backslash is not escaped +select to_json(text '\uabcd'); + to_json +---------- + "\uabcd" +(1 row) + +-- any other backslash is escaped +select to_json(text '\abcd'); + to_json +---------- + "\\abcd" +(1 row) + --json_agg SELECT json_agg(q) FROM ( SELECT $$a$$ || x AS b, y AS c, diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index ae7c5068119..1e46939b6fe 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -61,9 +61,9 @@ LINE 1: SELECT '"\u000g"'::jsonb; DETAIL: "\u" must be followed by four hexadecimal digits. CONTEXT: JSON data, line 1: "\u000g... SELECT '"\u0000"'::jsonb; -- OK, legal escape - jsonb ------------ - "\\u0000" + jsonb +---------- + "\u0000" (1 row) -- use octet_length here so we don't get an odd unicode char in the diff --git a/src/test/regress/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out index 38a95b43f8c..955dc424dce 100644 --- a/src/test/regress/expected/jsonb_1.out +++ b/src/test/regress/expected/jsonb_1.out @@ -61,9 +61,9 @@ LINE 1: SELECT '"\u000g"'::jsonb; DETAIL: "\u" must be followed by four hexadecimal digits. CONTEXT: JSON data, line 1: "\u000g... SELECT '"\u0000"'::jsonb; -- OK, legal escape - jsonb ------------ - "\\u0000" + jsonb +---------- + "\u0000" (1 row) -- use octet_length here so we don't get an odd unicode char in the diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 6c2faeccd30..3d5ed50126e 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -111,6 +111,14 @@ SET LOCAL TIME ZONE -8; select to_json(timestamptz '2014-05-28 12:22:35.614298-04'); COMMIT; +-- unicode escape - backslash is not escaped + +select to_json(text '\uabcd'); + +-- any other backslash is escaped + +select to_json(text '\abcd'); + --json_agg SELECT json_agg(q)