Hi all

Whenever I try to work with the new json types I trip over the lack of a function to escape text to json. The attached patch against master provides one by exposing the existing datum_to_json function to SQL. I've used the name value_to_json, but I'm not sure it's necessarily the right choice of name.

Please consider this for the 9.2 branch as well as to HEAD, as IMO it's very important for basic usability of the json functionality. It applies to 9.2 fine and passes "make check". I know it's late in the game, but it's also a very small change and it's very hard to build up JSON data structures other than simple rows or arrays without at minimum a way of escaping `text' to json strings.

This feels basic enough that I'm wondering if there's a reason it wasn't included from the start, but I don't see any comments in json.c talking about anything like this, nor did I find any -hackers discussion about it. I suspect it's just an oversight.

As value_to_json directly wraps datum_to_json it actually accepts record and array types too. I didn't see any reason to prevent that and force the user to instead use row_to_json or array_to_json for those cases. If you don't want to accept this, I can provide a wrapper for escape_json that only accepts a text argument instead, but I think *some* way to escape text to JSON is vital to have in 9.2.

A docs patch will follow shortly if you're happy that this patch is reasonable.

--
Craig Ringer
>From e829c8500b0e507cb70c1a87784c9395269e27bc Mon Sep 17 00:00:00 2001
From: Craig Ringer <ring...@ringerc.id.au>
Date: Mon, 13 Aug 2012 10:29:40 +0800
Subject: [PATCH] Implement value_to_json, exposing the existing datum_to_json
 for use from SQL.

A generic json quoting function needs to be available from SQL to permit
the building of any JSON structures other than those produced from arrays
or rowtypes.
---
 src/backend/utils/adt/json.c       | 28 ++++++++++++
 src/include/catalog/pg_proc.h      |  4 ++
 src/include/utils/json.h           |  1 +
 src/test/regress/expected/json.out | 91 ++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/json.sql      | 39 ++++++++++++++++
 5 files changed, 163 insertions(+)

diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
new file mode 100644
index 0425ac6..11babd3
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
*************** row_to_json_pretty(PG_FUNCTION_ARGS)
*** 1121,1126 ****
--- 1121,1154 ----
  }
  
  /*
+  * SQL function value_to_json(value)
+  *
+  * Wraps datum_to_json for use from SQL, so any element may be
+  * converted to json.
+  */
+ extern Datum
+ value_to_json(PG_FUNCTION_ARGS)
+ {
+ 	Datum		arg0 = PG_GETARG_DATUM(0);
+ 	Oid			arg0type = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ 	StringInfo	result;
+ 	TYPCATEGORY tcategory;
+ 	Oid			typoutput;
+ 	bool		typisvarlena;
+ 
+ 	if (arg0type == JSONOID)
+ 		tcategory = TYPCATEGORY_JSON;
+ 	else
+ 		tcategory = TypeCategory(arg0type);
+ 	
+ 	getTypeOutputInfo(arg0type, &typoutput, &typisvarlena);
+ 
+ 	result = makeStringInfo();
+ 	datum_to_json(arg0, PG_ARGISNULL(0), result, tcategory, typoutput);
+ 	PG_RETURN_TEXT_P(cstring_to_text(result->data));
+ }
+ 
+ /*
   * Produce a JSON string literal, properly escaping characters in the text.
   */
  void
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 665918f..31e7772
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 3155 (  row_to_json
*** 4091,4096 ****
--- 4091,4100 ----
  DESCR("map row to json");
  DATA(insert OID = 3156 (  row_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "2249 16" _null_ _null_ _null_ _null_ row_to_json_pretty _null_ _null_ _null_ ));
  DESCR("map row to json with optional pretty printing");
+ DATA(insert OID = 3164 (  value_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 114 "2283" _null_ _null_ _null_ _null_ value_to_json _null_ _null_ _null_ ));
+ DESCR("Convert a simple value to json, quoting and escaping if necessary");
+ DATA(insert OID = 3169 (  value_to_json	   PGNSP PGUID 12 1 0 0 0 f f f f f f s 1 0 114 "25" _null_ _null_ _null_ _null_ value_to_json _null_ _null_ _null_ ));
+ DESCR("Convert a simple value to json, quoting and escaping if necessary");
  
  /* uuid */
  DATA(insert OID = 2952 (  uuid_in		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ ));
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
new file mode 100644
index 0f38147..432ad24
*** a/src/include/utils/json.h
--- b/src/include/utils/json.h
*************** extern Datum array_to_json(PG_FUNCTION_A
*** 25,30 ****
--- 25,31 ----
  extern Datum array_to_json_pretty(PG_FUNCTION_ARGS);
  extern Datum row_to_json(PG_FUNCTION_ARGS);
  extern Datum row_to_json_pretty(PG_FUNCTION_ARGS);
+ extern Datum value_to_json(PG_FUNCTION_ARGS);
  extern void escape_json(StringInfo buf, const char *str);
  
  #endif   /* JSON_H */
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
new file mode 100644
index 2dfe7bb..41e011d
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
*************** FROM (SELECT '{"a":1,"b": [2,3,4,"d","e"
*** 433,435 ****
--- 433,526 ----
   {"jsonfield":{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}}
  (1 row)
  
+ -- value_to_json(json)
+ SELECT value_to_json('{"a":1}'::json);
+  value_to_json 
+ ---------------
+  {"a":1}
+ (1 row)
+ 
+ -- value_to_json(text)
+ SELECT value_to_json('{"a":1}'::text);
+  value_to_json 
+ ---------------
+  "{\"a\":1}"
+ (1 row)
+ 
+ -- value_to_json(unspecified-text)
+ SELECT value_to_json('{"a":1}');
+  value_to_json 
+ ---------------
+  "{\"a\":1}"
+ (1 row)
+ 
+ -- value_to_json(integer)
+ SELECT value_to_json(24);
+  value_to_json 
+ ---------------
+  24
+ (1 row)
+ 
+ -- value_to_json(float)
+ SELECT value_to_json('24.9'::float8);
+  value_to_json 
+ ---------------
+  24.9
+ (1 row)
+ 
+ -- value_to_json(NaN)
+ SELECT value_to_json('NaN'::float8);
+  value_to_json 
+ ---------------
+  "NaN"
+ (1 row)
+ 
+ -- value_to_json(Infinity)
+ SELECT value_to_json('Infinity'::float8);
+  value_to_json 
+ ---------------
+  "Infinity"
+ (1 row)
+ 
+ -- value_to_json(-Infinity)
+ SELECT value_to_json('-Infinity'::float8);
+  value_to_json 
+ ---------------
+  "-Infinity"
+ (1 row)
+ 
+ -- value_to_json(json[])
+ SELECT value_to_json(ARRAY[ '1'::json, '"4"'::json, '{"a":1}'::json ]::json[]);
+   value_to_json  
+ -----------------
+  [1,"4",{"a":1}]
+ (1 row)
+ 
+ -- value_to_json(text[])
+ SELECT value_to_json(ARRAY[ '1', '"4"', '{"a":1}' ]::text[]);
+        value_to_json       
+ ---------------------------
+  ["1","\"4\"","{\"a\":1}"]
+ (1 row)
+ 
+ -- value_to_json(int[])
+ SELECT value_to_json(ARRAY[1,2,3,4,-1]);
+  value_to_json 
+ ---------------
+  [1,2,3,4,-1]
+ (1 row)
+ 
+ -- value_to_json(row)
+ SELECT value_to_json(row(1,'foo'));
+  value_to_json 
+ ---------------
+  "(1,foo)"
+ (1 row)
+ 
+ -- value_to_json(emptyrow)
+ SELECT value_to_json(row());
+  value_to_json 
+ ---------------
+  "()"
+ (1 row)
+ 
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
new file mode 100644
index 52be0cf..8a0c3a5
*** a/src/test/regress/sql/json.sql
--- b/src/test/regress/sql/json.sql
*************** FROM (SELECT '-Infinity'::float8 AS "flo
*** 113,115 ****
--- 113,154 ----
  -- json input
  SELECT row_to_json(q)
  FROM (SELECT '{"a":1,"b": [2,3,4,"d","e","f"],"c":{"p":1,"q":2}}'::json AS "jsonfield") q;
+ 
+ -- value_to_json(json)
+ SELECT value_to_json('{"a":1}'::json);
+ 
+ -- value_to_json(text)
+ SELECT value_to_json('{"a":1}'::text);
+ 
+ -- value_to_json(unspecified-text)
+ SELECT value_to_json('{"a":1}');
+ 
+ -- value_to_json(integer)
+ SELECT value_to_json(24);
+ 
+ -- value_to_json(float)
+ SELECT value_to_json('24.9'::float8);
+ 
+ -- value_to_json(NaN)
+ SELECT value_to_json('NaN'::float8);
+ 
+ -- value_to_json(Infinity)
+ SELECT value_to_json('Infinity'::float8);
+ 
+ -- value_to_json(-Infinity)
+ SELECT value_to_json('-Infinity'::float8);
+ 
+ -- value_to_json(json[])
+ SELECT value_to_json(ARRAY[ '1'::json, '"4"'::json, '{"a":1}'::json ]::json[]);
+ 
+ -- value_to_json(text[])
+ SELECT value_to_json(ARRAY[ '1', '"4"', '{"a":1}' ]::text[]);
+ 
+ -- value_to_json(int[])
+ SELECT value_to_json(ARRAY[1,2,3,4,-1]);
+ 
+ -- value_to_json(row)
+ SELECT value_to_json(row(1,'foo'));
+ 
+ -- value_to_json(emptyrow)
+ SELECT value_to_json(row());
-- 
1.7.11.2

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to