On Fri, Jul 10, 2015 at 5:16 PM, Pavel Stehule <pavel.steh...@gmail.com>
wrote:

>
>> Well, one could call it premature pessimization due to dynamic call
>> overhead.
>>
>> IMO, the fact that json_out_init_context() sets the value callback to
>> json_out_value is an implementation detail, the other parts of code should
>> not rely on.  And for the Explain output, there definitely going to be
>> *some* code between context initialization and output callbacks: these are
>> done in a number of different functions.
>>
>
> Again - it is necessary? Postgres still use modular code, not OOP code. I
> can understand the using of this technique, when I need a possibility to
> change behave. But these function are used for printing JSON, not printing
> any others.
>

No, it's not strictly necessary.

For me it's not about procedural- vs. object- style, but rather about being
able to override/extend the behavior consistently.  And for that I would
prefer that if I override the value callback in a JSON output context, that
it would be called for every value being printed, not only for some of them.

Thank you for pointing out the case of Explain format, I've totally
overlooked it in my first version.  Trying to apply the proposed approach
in the explain printing code led me to reorganize things slightly.  I've
added explicit functions for printing keys vs. values, thus no need to
expose that key_scalar param anymore.  There are now separate before/after
key and before/after value functions as well, but I believe it makes for a
cleaner code.

The most of the complexity is still in the code that decides whether or not
to put spaces (between the values or for indentation) and newlines at
certain points.  Should we decide to unify the style we emit ourselves,
this could be simplified, while still leaving room for great flexibility if
overridden by an extension, for example.

Have a nice weekend.
--
Alex
diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c
new file mode 100644
index 7d89867..1f365f5
*** a/contrib/hstore/hstore_io.c
--- b/contrib/hstore/hstore_io.c
*************** hstore_to_json_loose(PG_FUNCTION_ARGS)
*** 1241,1286 ****
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData tmp,
! 				dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	initStringInfo(&dst);
! 
! 	appendStringInfoChar(&dst, '{');
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		escape_json(&dst, tmp.data);
! 		appendStringInfoString(&dst, ": ");
  		if (HS_VALISNULL(entries, i))
! 			appendStringInfoString(&dst, "null");
  		/* guess that values of 't' or 'f' are booleans */
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't')
! 			appendStringInfoString(&dst, "true");
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f')
! 			appendStringInfoString(&dst, "false");
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
  			if (IsValidJsonNumber(tmp.data, tmp.len))
! 				appendBinaryStringInfo(&dst, tmp.data, tmp.len);
  			else
! 				escape_json(&dst, tmp.data);
  		}
- 
- 		if (i + 1 != count)
- 			appendStringInfoString(&dst, ", ");
  	}
- 	appendStringInfoChar(&dst, '}');
  
! 	PG_RETURN_TEXT_P(cstring_to_text(dst.data));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_json);
--- 1241,1293 ----
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData	tmp;
! 	Datum			num;
! 	JsonOutContext	dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	json_out_init_context(&dst, JSON_OUT_USE_SPACES);
! 	dst.object_start(&dst);
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		json_out_cstring_key(&dst, tmp.data);
! 
  		if (HS_VALISNULL(entries, i))
! 			dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid);
! 
  		/* guess that values of 't' or 'f' are booleans */
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't')
! 			dst.value(&dst, BoolGetDatum(true), JSONTYPE_BOOL,
! 					  InvalidOid, InvalidOid);
! 
  		else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f')
! 			dst.value(&dst, BoolGetDatum(false), JSONTYPE_BOOL,
! 					  InvalidOid, InvalidOid);
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
+ 
  			if (IsValidJsonNumber(tmp.data, tmp.len))
! 			{
! 				num = DirectFunctionCall3(numeric_in, CStringGetDatum(tmp.data), 0, -1);
! 				dst.value(&dst, num, JSONTYPE_NUMERIC,
! 						  NUMERICOID, 1702 /* numeric_out */);
! 				pfree(DatumGetPointer(num));
! 			}
  			else
! 				json_out_cstring_value(&dst, tmp.data);
  		}
  	}
  
! 	dst.object_end(&dst);
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.result.data, dst.result.len));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_json);
*************** hstore_to_json(PG_FUNCTION_ARGS)
*** 1292,1329 ****
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData tmp,
! 				dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	initStringInfo(&dst);
! 
! 	appendStringInfoChar(&dst, '{');
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		escape_json(&dst, tmp.data);
! 		appendStringInfoString(&dst, ": ");
  		if (HS_VALISNULL(entries, i))
! 			appendStringInfoString(&dst, "null");
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
! 			escape_json(&dst, tmp.data);
  		}
- 
- 		if (i + 1 != count)
- 			appendStringInfoString(&dst, ", ");
  	}
- 	appendStringInfoChar(&dst, '}');
  
! 	PG_RETURN_TEXT_P(cstring_to_text(dst.data));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_jsonb);
--- 1299,1332 ----
  	int			count = HS_COUNT(in);
  	char	   *base = STRPTR(in);
  	HEntry	   *entries = ARRPTR(in);
! 	StringInfoData	tmp;
! 	JsonOutContext	dst;
  
  	if (count == 0)
  		PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2));
  
  	initStringInfo(&tmp);
! 	json_out_init_context(&dst, JSON_OUT_USE_SPACES);
! 	dst.object_start(&dst);
  
  	for (i = 0; i < count; i++)
  	{
  		resetStringInfo(&tmp);
  		appendBinaryStringInfo(&tmp, HS_KEY(entries, base, i), HS_KEYLEN(entries, i));
! 		json_out_cstring_key(&dst, tmp.data);
! 
  		if (HS_VALISNULL(entries, i))
! 			dst.value(&dst, (Datum) 0, JSONTYPE_NULL, InvalidOid, InvalidOid);
  		else
  		{
  			resetStringInfo(&tmp);
  			appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
! 			json_out_cstring_value(&dst, tmp.data);
  		}
  	}
  
! 	dst.object_end(&dst);
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.result.data, dst.result.len));
  }
  
  PG_FUNCTION_INFO_V1(hstore_to_jsonb);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
new file mode 100644
index 0d1ecc2..19c7db2
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void ExplainCloseGroup(const char
*** 126,132 ****
  static void ExplainDummyGroup(const char *objtype, const char *labelname,
  				  ExplainState *es);
  static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
- static void ExplainJSONLineEnding(ExplainState *es);
  static void ExplainYAMLLineStarting(ExplainState *es);
  static void escape_yaml(StringInfo buf, const char *str);
  
--- 126,131 ----
*************** ExplainQuery(ExplainStmt *stmt, const ch
*** 173,179 ****
--- 172,189 ----
  			else if (strcmp(p, "xml") == 0)
  				es->format = EXPLAIN_FORMAT_XML;
  			else if (strcmp(p, "json") == 0)
+ 			{
  				es->format = EXPLAIN_FORMAT_JSON;
+ 
+ 				json_out_init_context(&es->json_cxt,
+ 									  JSON_OUT_USE_SPACES | JSON_OUT_USE_LINE_FEEDS);
+ 				es->json_cxt.indent_spaces = 2;
+ 
+ 				/* point our result buffer straight to json output context */
+ 				pfree(es->str->data);
+ 				pfree(es->str);
+ 				es->str = &es->json_cxt.result;
+ 			}
  			else if (strcmp(p, "yaml") == 0)
  				es->format = EXPLAIN_FORMAT_YAML;
  			else
*************** ExplainPropertyList(const char *qlabel,
*** 2711,2728 ****
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			ExplainJSONLineEnding(es);
! 			appendStringInfoSpaces(es->str, es->indent * 2);
! 			escape_json(es->str, qlabel);
! 			appendStringInfoString(es->str, ": [");
  			foreach(lc, data)
  			{
! 				if (!first)
! 					appendStringInfoString(es->str, ", ");
! 				escape_json(es->str, (const char *) lfirst(lc));
! 				first = false;
  			}
! 			appendStringInfoChar(es->str, ']');
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 2721,2733 ----
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			json_out_cstring_key(&es->json_cxt, qlabel);
! 			es->json_cxt.array_start(&es->json_cxt);
  			foreach(lc, data)
  			{
! 				json_out_cstring_value(&es->json_cxt, (const char *) lfirst(lc));
  			}
! 			es->json_cxt.array_end(&es->json_cxt);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainPropertyListNested(const char *ql
*** 2757,2773 ****
  			return;
  
  		case EXPLAIN_FORMAT_JSON:
! 			ExplainJSONLineEnding(es);
! 			appendStringInfoSpaces(es->str, es->indent * 2);
! 			appendStringInfoChar(es->str, '[');
  			foreach(lc, data)
  			{
! 				if (!first)
! 					appendStringInfoString(es->str, ", ");
! 				escape_json(es->str, (const char *) lfirst(lc));
! 				first = false;
  			}
! 			appendStringInfoChar(es->str, ']');
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 2762,2773 ----
  			return;
  
  		case EXPLAIN_FORMAT_JSON:
! 			es->json_cxt.array_start(&es->json_cxt);
  			foreach(lc, data)
  			{
! 				json_out_cstring_value(&es->json_cxt, (const char *) lfirst(lc));
  			}
! 			es->json_cxt.array_end(&es->json_cxt);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainProperty(const char *qlabel, cons
*** 2820,2833 ****
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			ExplainJSONLineEnding(es);
! 			appendStringInfoSpaces(es->str, es->indent * 2);
! 			escape_json(es->str, qlabel);
! 			appendStringInfoString(es->str, ": ");
! 			if (numeric)
! 				appendStringInfoString(es->str, value);
! 			else
! 				escape_json(es->str, value);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 2820,2839 ----
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			{
! 				Datum		num;
! 
! 				json_out_cstring_key(&es->json_cxt, qlabel);
! 				if (numeric)
! 				{
! 					num = DirectFunctionCall3(numeric_in, CStringGetDatum(value), 0, -1);
! 					es->json_cxt.value(&es->json_cxt, num, JSONTYPE_NUMERIC,
! 									   NUMERICOID, 1702 /* numeric_out */);
! 					pfree(DatumGetPointer(num));
! 				}
! 				else
! 					json_out_cstring_value(&es->json_cxt, value);
! 			}
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainOpenGroup(const char *objtype, co
*** 2913,2935 ****
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
- 			ExplainJSONLineEnding(es);
- 			appendStringInfoSpaces(es->str, 2 * es->indent);
  			if (labelname)
! 			{
! 				escape_json(es->str, labelname);
! 				appendStringInfoString(es->str, ": ");
! 			}
! 			appendStringInfoChar(es->str, labeled ? '{' : '[');
  
! 			/*
! 			 * In JSON format, the grouping_stack is an integer list.  0 means
! 			 * we've emitted nothing at this grouping level, 1 means we've
! 			 * emitted something (and so the next item needs a comma). See
! 			 * ExplainJSONLineEnding().
! 			 */
! 			es->grouping_stack = lcons_int(0, es->grouping_stack);
! 			es->indent++;
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 2919,2931 ----
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
  			if (labelname)
! 				json_out_cstring_key(&es->json_cxt, labelname);
  
! 			if (labeled)
! 				es->json_cxt.object_start(&es->json_cxt);
! 			else
! 				es->json_cxt.array_start(&es->json_cxt);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainCloseGroup(const char *objtype, c
*** 2976,2986 ****
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			es->indent--;
! 			appendStringInfoChar(es->str, '\n');
! 			appendStringInfoSpaces(es->str, 2 * es->indent);
! 			appendStringInfoChar(es->str, labeled ? '}' : ']');
! 			es->grouping_stack = list_delete_first(es->grouping_stack);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 2972,2981 ----
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			if (labeled)
! 				es->json_cxt.object_end(&es->json_cxt);
! 			else
! 				es->json_cxt.array_end(&es->json_cxt);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainDummyGroup(const char *objtype, c
*** 3010,3023 ****
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
- 			ExplainJSONLineEnding(es);
- 			appendStringInfoSpaces(es->str, 2 * es->indent);
  			if (labelname)
! 			{
! 				escape_json(es->str, labelname);
! 				appendStringInfoString(es->str, ": ");
! 			}
! 			escape_json(es->str, objtype);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 3005,3014 ----
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
  			if (labelname)
! 				json_out_cstring_key(&es->json_cxt, labelname);
! 
! 			json_out_cstring_value(&es->json_cxt, objtype);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainBeginOutput(ExplainState *es)
*** 3059,3067 ****
  
  		case EXPLAIN_FORMAT_JSON:
  			/* top-level structure is an array of plans */
! 			appendStringInfoChar(es->str, '[');
! 			es->grouping_stack = lcons_int(0, es->grouping_stack);
! 			es->indent++;
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 3050,3056 ----
  
  		case EXPLAIN_FORMAT_JSON:
  			/* top-level structure is an array of plans */
! 			es->json_cxt.array_start(&es->json_cxt);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainEndOutput(ExplainState *es)
*** 3088,3096 ****
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			es->indent--;
! 			appendStringInfoString(es->str, "\n]");
! 			es->grouping_stack = list_delete_first(es->grouping_stack);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
--- 3077,3083 ----
  			break;
  
  		case EXPLAIN_FORMAT_JSON:
! 			es->json_cxt.array_end(&es->json_cxt);
  			break;
  
  		case EXPLAIN_FORMAT_YAML:
*************** ExplainXMLTag(const char *tagname, int f
*** 3150,3173 ****
  }
  
  /*
-  * Emit a JSON line ending.
-  *
-  * JSON requires a comma after each property but the last.  To facilitate this,
-  * in JSON format, the text emitted for each property begins just prior to the
-  * preceding line-break (and comma, if applicable).
-  */
- static void
- ExplainJSONLineEnding(ExplainState *es)
- {
- 	Assert(es->format == EXPLAIN_FORMAT_JSON);
- 	if (linitial_int(es->grouping_stack) != 0)
- 		appendStringInfoChar(es->str, ',');
- 	else
- 		linitial_int(es->grouping_stack) = 1;
- 	appendStringInfoChar(es->str, '\n');
- }
- 
- /*
   * Indent a YAML line.
   *
   * YAML lines are ordinarily indented by two spaces per indentation level.
--- 3137,3142 ----
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
new file mode 100644
index 26d3843..48dcc6b
*** a/src/backend/utils/adt/json.c
--- b/src/backend/utils/adt/json.c
*************** typedef enum					/* contexts of JSON par
*** 53,73 ****
  	JSON_PARSE_END				/* saw the end of a document, expect nothing */
  } JsonParseContext;
  
- typedef enum					/* type categories for datum_to_json */
- {
- 	JSONTYPE_NULL,				/* null, so we didn't bother to identify */
- 	JSONTYPE_BOOL,				/* boolean (built-in types only) */
- 	JSONTYPE_NUMERIC,			/* numeric (ditto) */
- 	JSONTYPE_DATE,				/* we use special formatting for datetimes */
- 	JSONTYPE_TIMESTAMP,
- 	JSONTYPE_TIMESTAMPTZ,
- 	JSONTYPE_JSON,				/* JSON itself (and JSONB) */
- 	JSONTYPE_ARRAY,				/* array */
- 	JSONTYPE_COMPOSITE,			/* composite */
- 	JSONTYPE_CAST,				/* something with an explicit cast to JSON */
- 	JSONTYPE_OTHER				/* all else */
- } JsonTypeCategory;
- 
  static inline void json_lex(JsonLexContext *lex);
  static inline void json_lex_string(JsonLexContext *lex);
  static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err);
--- 53,58 ----
*************** static void report_parse_error(JsonParse
*** 80,102 ****
  static void report_invalid_token(JsonLexContext *lex);
  static int	report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
! static void composite_to_json(Datum composite, StringInfo result,
! 				  bool use_line_feeds);
! static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
! 				  JsonTypeCategory tcategory, Oid outfuncoid,
! 				  bool use_line_feeds);
! static void array_to_json_internal(Datum array, StringInfo result,
! 					   bool use_line_feeds);
  static void json_categorize_type(Oid typoid,
  					 JsonTypeCategory *tcategory,
  					 Oid *outfuncoid);
! static void datum_to_json(Datum val, bool is_null, StringInfo result,
! 			  JsonTypeCategory tcategory, Oid outfuncoid,
! 			  bool key_scalar);
! static void add_json(Datum val, bool is_null, StringInfo result,
  		 Oid val_type, bool key_scalar);
- static text *catenate_stringinfo_string(StringInfo buffer, const char *addon);
  
  /* the null action object used for pure validation */
  static JsonSemAction nullSemAction =
--- 65,85 ----
  static void report_invalid_token(JsonLexContext *lex);
  static int	report_json_context(JsonLexContext *lex);
  static char *extract_mb_char(char *s);
! 
! static void json_out_comma(JsonOutContext *out);
! static void json_out_indent(JsonOutContext *out);
! static void json_out_internal(JsonOutContext *out, Datum val,
! 				  JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid, bool escape);
! 
! static void array_dim_to_json(JsonOutContext *out, int dim, int ndims, int *dims,
  				  Datum *vals, bool *nulls, int *valcount,
! 				  JsonTypeCategory tcategory, Oid elemtypoid, Oid outfuncoid);
! static void array_to_json_internal(Datum array, JsonOutContext *out);
  static void json_categorize_type(Oid typoid,
  					 JsonTypeCategory *tcategory,
  					 Oid *outfuncoid);
! static void add_json(Datum val, bool is_null, JsonOutContext *out,
  		 Oid val_type, bool key_scalar);
  
  /* the null action object used for pure validation */
  static JsonSemAction nullSemAction =
*************** json_categorize_type(Oid typoid,
*** 1377,1399 ****
   * If key_scalar is true, the value is being printed as a key, so insist
   * it's of an acceptable type, and force it to be quoted.
   */
! static void
! datum_to_json(Datum val, bool is_null, StringInfo result,
! 			  JsonTypeCategory tcategory, Oid outfuncoid,
  			  bool key_scalar)
  {
- 	char	   *outputstr;
- 	text	   *jsontext;
- 
  	/* callers are expected to ensure that null keys are not passed in */
  	Assert(!(key_scalar && is_null));
  
- 	if (is_null)
- 	{
- 		appendStringInfoString(result, "null");
- 		return;
- 	}
- 
  	if (key_scalar &&
  		(tcategory == JSONTYPE_ARRAY ||
  		 tcategory == JSONTYPE_COMPOSITE ||
--- 1360,1373 ----
   * If key_scalar is true, the value is being printed as a key, so insist
   * it's of an acceptable type, and force it to be quoted.
   */
! void
! datum_to_json(Datum val, bool is_null, JsonOutContext *out,
! 			  JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid,
  			  bool key_scalar)
  {
  	/* callers are expected to ensure that null keys are not passed in */
  	Assert(!(key_scalar && is_null));
  
  	if (key_scalar &&
  		(tcategory == JSONTYPE_ARRAY ||
  		 tcategory == JSONTYPE_COMPOSITE ||
*************** datum_to_json(Datum val, bool is_null, S
*** 1406,1419 ****
  	switch (tcategory)
  	{
  		case JSONTYPE_ARRAY:
! 			array_to_json_internal(val, result, false);
  			break;
  		case JSONTYPE_COMPOSITE:
! 			composite_to_json(val, result, false);
  			break;
  		case JSONTYPE_BOOL:
  			outputstr = DatumGetBool(val) ? "true" : "false";
! 			if (key_scalar)
  				escape_json(result, outputstr);
  			else
  				appendStringInfoString(result, outputstr);
--- 1380,1591 ----
  	switch (tcategory)
  	{
  		case JSONTYPE_ARRAY:
! 			array_to_json_internal(val, out);
  			break;
  		case JSONTYPE_COMPOSITE:
! 			composite_to_json(val, out);
! 			break;
! 		default:
! 			if (key_scalar)
! 				out->key(out, val, tcategory, typoid, outfuncoid);
! 			else
! 				out->value(out, val, tcategory, typoid, outfuncoid);
! 			break;
! 	}
! }
! 
! void
! json_out_init_context(JsonOutContext *out, int flags)
! {
! 	out->object_start = json_out_object_start;
! 	out->object_end = json_out_object_end;
! 	out->array_start = json_out_array_start;
! 	out->array_end = json_out_array_end;
! 	out->before_key = json_out_before_key;
! 	out->key = json_out_key;
! 	out->after_key = json_out_after_key;
! 	out->before_value = json_out_before_value;
! 	out->value = json_out_value;
! 	out->after_value = json_out_after_value;
! 
! 	initStringInfo(&out->result);
! 	out->flags = flags;
! 	out->indent_spaces = 0;
! 	out->need_comma = false;
! 	out->need_colon = false;
! 	out->depth = 0;
! 
! 	out->agg_tcategory = JSONTYPE_OTHER;
! 	out->agg_outfuncoid = InvalidOid;
! }
! 
! void
! json_out_object_start(JsonOutContext *out)
! {
! 	out->before_value(out);
! 
! 	appendStringInfoChar(&out->result, '{');
! 	if (out->indent_spaces > 0)
! 		appendStringInfoChar(&out->result, '\n');
! 
! 	out->depth++;
! }
! 
! void
! json_out_object_end(JsonOutContext *out)
! {
! 	if (out->indent_spaces > 0)
! 		appendStringInfoChar(&out->result, '\n');
! 
! 	out->depth--;
! 
! 	json_out_indent(out);
! 	appendStringInfoChar(&out->result, '}');
! 
! 	out->after_value(out);
! }
! 
! void
! json_out_array_start(JsonOutContext *out)
! {
! 	out->before_value(out);
! 
! 	appendStringInfoChar(&out->result, '[');
! 	if (out->indent_spaces > 0)
! 		appendStringInfoChar(&out->result, '\n');
! 
! 	out->depth++;
! }
! 
! void
! json_out_array_end(JsonOutContext *out)
! {
! 	if (out->indent_spaces > 0)
! 		appendStringInfoChar(&out->result, '\n');
! 
! 	out->depth--;
! 
! 	json_out_indent(out);
! 	appendStringInfoChar(&out->result, ']');
! 
! 	out->after_value(out);
! }
! 
! void
! json_out_before_key(JsonOutContext *out)
! {
! 	Assert(!out->need_colon);
! 
! 	json_out_comma(out);
! 	json_out_indent(out);
! }
! 
! void
! json_out_after_key(JsonOutContext *out)
! {
! 	out->need_colon = true;
! 	out->need_comma = false;
! }
! 
! void
! json_out_before_value(JsonOutContext *out)
! {
! 	json_out_comma(out);
! 
! 	if (out->need_colon)
! 	{
! 		out->need_colon = false;
! 		appendStringInfoChar(&out->result, ':');
! 
! 		if ((out->flags & JSON_OUT_USE_SPACES) != 0)
! 			appendStringInfoChar(&out->result, ' ');
! 	}
! 	else
! 		json_out_indent(out);
! }
! 
! void
! json_out_after_value(JsonOutContext *out)
! {
! 	out->need_comma = true;
! }
! 
! static void
! json_out_comma(JsonOutContext *out)
! {
! 	if (out->need_comma)
! 	{
! 		out->need_comma = false;
! 		appendStringInfoChar(&out->result, ',');
! 
! 		/* don't get into all the prettiness deep in the object structure,
! 		 * unless indentation was required */
! 		if (out->depth == 1 || out->indent_spaces > 0)
! 		{
! 			if ((out->flags & JSON_OUT_USE_SPACES) != 0 &&
! 				(out->flags & JSON_OUT_USE_LINE_FEEDS) == 0)
! 				appendStringInfoChar(&out->result, ' ');
! 
! 			if ((out->flags & JSON_OUT_USE_LINE_FEEDS) != 0)
! 			{
! 				appendStringInfoChar(&out->result, '\n');
! 
! 				/* this is only to support json_agg's current output style;
! 				 * but if indent is set we don't need the extra space */
! 				if (out->indent_spaces <= 0)
! 					appendStringInfoChar(&out->result, ' ');
! 			}
! 		}
! 	}
! }
! 
! static void
! json_out_indent(JsonOutContext *out)
! {
! 	if (out->indent_spaces > 0)
! 		appendStringInfoSpaces(&out->result, out->depth * out->indent_spaces);
! }
! 
! void
! json_out_key(JsonOutContext *out, Datum val, JsonTypeCategory tcategory,
! 			 Oid typoid, Oid outfuncoid)
! {
! 	out->before_key(out);
! 
! 	json_out_internal(out, val, tcategory, typoid, outfuncoid, true);
! 
! 	out->after_key(out);
! }
! 
! void
! json_out_value(JsonOutContext *out, Datum val, JsonTypeCategory tcategory,
! 			   Oid typoid, Oid outfuncoid)
! {
! 	out->before_value(out);
! 
! 	json_out_internal(out, val, tcategory, typoid, outfuncoid, false);
! 
! 	out->after_value(out);
! }
! 
! static void
! json_out_internal(JsonOutContext *out, Datum val, JsonTypeCategory tcategory,
! 				  Oid typoid, Oid outfuncoid, bool escape)
! {
! 	char	   *outputstr;
! 	text	   *jsontext;
! 	StringInfo	result = &out->result;
! 
! 	Assert(!(tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE));
! 
! 	switch (tcategory)
! 	{
! 		case JSONTYPE_NULL:
! 			appendStringInfoString(result, "null");
  			break;
  		case JSONTYPE_BOOL:
  			outputstr = DatumGetBool(val) ? "true" : "false";
! 			if (escape)
  				escape_json(result, outputstr);
  			else
  				appendStringInfoString(result, outputstr);
*************** datum_to_json(Datum val, bool is_null, S
*** 1425,1431 ****
  			 * Don't call escape_json for a non-key if it's a valid JSON
  			 * number.
  			 */
! 			if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr)))
  				appendStringInfoString(result, outputstr);
  			else
  				escape_json(result, outputstr);
--- 1597,1603 ----
  			 * Don't call escape_json for a non-key if it's a valid JSON
  			 * number.
  			 */
! 			if (!escape && IsValidJsonNumber(outputstr, strlen(outputstr)))
  				appendStringInfoString(result, outputstr);
  			else
  				escape_json(result, outputstr);
*************** datum_to_json(Datum val, bool is_null, S
*** 1520,1581 ****
  			pfree(jsontext);
  			break;
  		default:
! 			outputstr = OidOutputFunctionCall(outfuncoid, val);
! 			escape_json(result, outputstr);
! 			pfree(outputstr);
  			break;
  	}
  }
  
  /*
   * Process a single dimension of an array.
   * If it's the innermost dimension, output the values, otherwise call
   * ourselves recursively to process the next dimension.
   */
  static void
! array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, Datum *vals,
  				  bool *nulls, int *valcount, JsonTypeCategory tcategory,
! 				  Oid outfuncoid, bool use_line_feeds)
  {
  	int			i;
- 	const char *sep;
  
  	Assert(dim < ndims);
  
! 	sep = use_line_feeds ? ",\n " : ",";
! 
! 	appendStringInfoChar(result, '[');
  
  	for (i = 1; i <= dims[dim]; i++)
  	{
- 		if (i > 1)
- 			appendStringInfoString(result, sep);
- 
  		if (dim + 1 == ndims)
  		{
! 			datum_to_json(vals[*valcount], nulls[*valcount], result, tcategory,
! 						  outfuncoid, false);
  			(*valcount)++;
  		}
  		else
! 		{
! 			/*
! 			 * Do we want line feeds on inner dimensions of arrays? For now
! 			 * we'll say no.
! 			 */
! 			array_dim_to_json(result, dim + 1, ndims, dims, vals, nulls,
! 							  valcount, tcategory, outfuncoid, false);
! 		}
  	}
  
! 	appendStringInfoChar(result, ']');
  }
  
  /*
   * Turn an array into JSON.
   */
  static void
! array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
  {
  	ArrayType  *v = DatumGetArrayTypeP(array);
  	Oid			element_type = ARR_ELEMTYPE(v);
--- 1692,1760 ----
  			pfree(jsontext);
  			break;
  		default:
! 			if (typoid == CSTRINGOID)
! 				escape_json(result, DatumGetCString(val));
! 			else
! 			{
! 				outputstr = OidOutputFunctionCall(outfuncoid, val);
! 				escape_json(result, outputstr);
! 				pfree(outputstr);
! 			}
  			break;
  	}
  }
  
+ void
+ json_out_cstring_key(JsonOutContext *out, const char *str)
+ {
+ 	out->key(out, CStringGetDatum(str), JSONTYPE_OTHER,
+ 			 CSTRINGOID, 2293 /* cstring_out */);
+ }
+ 
+ void
+ json_out_cstring_value(JsonOutContext *out, const char *str)
+ {
+ 	out->value(out, CStringGetDatum(str), JSONTYPE_OTHER,
+ 			   CSTRINGOID, 2293 /* cstring_out */);
+ }
+ 
  /*
   * Process a single dimension of an array.
   * If it's the innermost dimension, output the values, otherwise call
   * ourselves recursively to process the next dimension.
   */
  static void
! array_dim_to_json(JsonOutContext *out, int dim, int ndims, int *dims, Datum *vals,
  				  bool *nulls, int *valcount, JsonTypeCategory tcategory,
! 				  Oid elemtypoid, Oid outfuncoid)
  {
  	int			i;
  
  	Assert(dim < ndims);
  
! 	out->array_start(out);
  
  	for (i = 1; i <= dims[dim]; i++)
  	{
  		if (dim + 1 == ndims)
  		{
! 			datum_to_json(vals[*valcount], nulls[*valcount], out, tcategory,
! 						  elemtypoid, outfuncoid, false);
  			(*valcount)++;
  		}
  		else
! 			array_dim_to_json(out, dim + 1, ndims, dims, vals, nulls,
! 							  valcount, tcategory, elemtypoid, outfuncoid);
  	}
  
! 	out->array_end(out);
  }
  
  /*
   * Turn an array into JSON.
   */
  static void
! array_to_json_internal(Datum array, JsonOutContext *out)
  {
  	ArrayType  *v = DatumGetArrayTypeP(array);
  	Oid			element_type = ARR_ELEMTYPE(v);
*************** array_to_json_internal(Datum array, Stri
*** 1597,1603 ****
  
  	if (nitems <= 0)
  	{
! 		appendStringInfoString(result, "[]");
  		return;
  	}
  
--- 1776,1783 ----
  
  	if (nitems <= 0)
  	{
! 		out->array_start(out);
! 		out->array_end(out);
  		return;
  	}
  
*************** array_to_json_internal(Datum array, Stri
*** 1611,1618 ****
  					  typalign, &elements, &nulls,
  					  &nitems);
  
! 	array_dim_to_json(result, 0, ndim, dim, elements, nulls, &count, tcategory,
! 					  outfuncoid, use_line_feeds);
  
  	pfree(elements);
  	pfree(nulls);
--- 1791,1798 ----
  					  typalign, &elements, &nulls,
  					  &nitems);
  
! 	array_dim_to_json(out, 0, ndim, dim, elements, nulls, &count, tcategory,
! 					  element_type, outfuncoid);
  
  	pfree(elements);
  	pfree(nulls);
*************** array_to_json_internal(Datum array, Stri
*** 1621,1628 ****
  /*
   * Turn a composite / record into JSON.
   */
! static void
! composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
  {
  	HeapTupleHeader td;
  	Oid			tupType;
--- 1801,1808 ----
  /*
   * Turn a composite / record into JSON.
   */
! void
! composite_to_json(Datum composite, JsonOutContext *out)
  {
  	HeapTupleHeader td;
  	Oid			tupType;
*************** composite_to_json(Datum composite, Strin
*** 1631,1640 ****
  	HeapTupleData tmptup,
  			   *tuple;
  	int			i;
- 	bool		needsep = false;
- 	const char *sep;
- 
- 	sep = use_line_feeds ? ",\n " : ",";
  
  	td = DatumGetHeapTupleHeader(composite);
  
--- 1811,1816 ----
*************** composite_to_json(Datum composite, Strin
*** 1648,1654 ****
  	tmptup.t_data = td;
  	tuple = &tmptup;
  
! 	appendStringInfoChar(result, '{');
  
  	for (i = 0; i < tupdesc->natts; i++)
  	{
--- 1824,1830 ----
  	tmptup.t_data = td;
  	tuple = &tmptup;
  
! 	out->object_start(out);
  
  	for (i = 0; i < tupdesc->natts; i++)
  	{
*************** composite_to_json(Datum composite, Strin
*** 1661,1676 ****
  		if (tupdesc->attrs[i]->attisdropped)
  			continue;
  
- 		if (needsep)
- 			appendStringInfoString(result, sep);
- 		needsep = true;
- 
  		attname = NameStr(tupdesc->attrs[i]->attname);
! 		escape_json(result, attname);
! 		appendStringInfoChar(result, ':');
  
  		val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
- 
  		if (isnull)
  		{
  			tcategory = JSONTYPE_NULL;
--- 1837,1846 ----
  		if (tupdesc->attrs[i]->attisdropped)
  			continue;
  
  		attname = NameStr(tupdesc->attrs[i]->attname);
! 		json_out_cstring_key(out, attname);
  
  		val = heap_getattr(tuple, i + 1, tupdesc, &isnull);
  		if (isnull)
  		{
  			tcategory = JSONTYPE_NULL;
*************** composite_to_json(Datum composite, Strin
*** 1680,1689 ****
  			json_categorize_type(tupdesc->attrs[i]->atttypid,
  								 &tcategory, &outfuncoid);
  
! 		datum_to_json(val, isnull, result, tcategory, outfuncoid, false);
  	}
  
! 	appendStringInfoChar(result, '}');
  	ReleaseTupleDesc(tupdesc);
  }
  
--- 1850,1861 ----
  			json_categorize_type(tupdesc->attrs[i]->atttypid,
  								 &tcategory, &outfuncoid);
  
! 		datum_to_json(val, isnull, out, tcategory,
! 					  tupdesc->attrs[i]->atttypid, outfuncoid, false);
  	}
  
! 	out->object_end(out);
! 
  	ReleaseTupleDesc(tupdesc);
  }
  
*************** composite_to_json(Datum composite, Strin
*** 1695,1701 ****
   * lookups only once.
   */
  static void
! add_json(Datum val, bool is_null, StringInfo result,
  		 Oid val_type, bool key_scalar)
  {
  	JsonTypeCategory tcategory;
--- 1867,1873 ----
   * lookups only once.
   */
  static void
! add_json(Datum val, bool is_null, JsonOutContext *out,
  		 Oid val_type, bool key_scalar)
  {
  	JsonTypeCategory tcategory;
*************** add_json(Datum val, bool is_null, String
*** 1712,1721 ****
  		outfuncoid = InvalidOid;
  	}
  	else
! 		json_categorize_type(val_type,
! 							 &tcategory, &outfuncoid);
  
! 	datum_to_json(val, is_null, result, tcategory, outfuncoid, key_scalar);
  }
  
  /*
--- 1884,1892 ----
  		outfuncoid = InvalidOid;
  	}
  	else
! 		json_categorize_type(val_type, &tcategory, &outfuncoid);
  
! 	datum_to_json(val, is_null, out, tcategory, val_type, outfuncoid, key_scalar);
  }
  
  /*
*************** extern Datum
*** 1725,1737 ****
  array_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	array_to_json_internal(array, result, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1896,1907 ----
  array_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, 0);
! 	array_to_json_internal(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** array_to_json_pretty(PG_FUNCTION_ARGS)
*** 1742,1754 ****
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	array_to_json_internal(array, result, use_line_feeds);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1912,1923 ----
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, use_line_feeds ? JSON_OUT_USE_LINE_FEEDS : 0);
! 	array_to_json_internal(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** extern Datum
*** 1758,1770 ****
  row_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	composite_to_json(array, result, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1927,1938 ----
  row_to_json(PG_FUNCTION_ARGS)
  {
  	Datum		array = PG_GETARG_DATUM(0);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, 0);
! 	composite_to_json(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** row_to_json_pretty(PG_FUNCTION_ARGS)
*** 1775,1787 ****
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	StringInfo	result;
! 
! 	result = makeStringInfo();
  
! 	composite_to_json(array, result, use_line_feeds);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1943,1954 ----
  {
  	Datum		array = PG_GETARG_DATUM(0);
  	bool		use_line_feeds = PG_GETARG_BOOL(1);
! 	JsonOutContext	out;
  
! 	json_out_init_context(&out, use_line_feeds ? JSON_OUT_USE_LINE_FEEDS : 0);
! 	composite_to_json(array, &out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** to_json(PG_FUNCTION_ARGS)
*** 1792,1800 ****
  {
  	Datum		val = PG_GETARG_DATUM(0);
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
- 	StringInfo	result;
  	JsonTypeCategory tcategory;
  	Oid			outfuncoid;
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
--- 1959,1969 ----
  {
  	Datum		val = PG_GETARG_DATUM(0);
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
  	JsonTypeCategory tcategory;
  	Oid			outfuncoid;
+ 	JsonOutContext	out;
+ 
+ 	json_out_init_context(&out, 0);
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
*************** to_json(PG_FUNCTION_ARGS)
*** 1804,1814 ****
  	json_categorize_type(val_type,
  						 &tcategory, &outfuncoid);
  
! 	result = makeStringInfo();
! 
! 	datum_to_json(val, false, result, tcategory, outfuncoid, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 1973,1981 ----
  	json_categorize_type(val_type,
  						 &tcategory, &outfuncoid);
  
! 	datum_to_json(val, false, &out, tcategory, val_type, outfuncoid, false);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_agg_transfn(PG_FUNCTION_ARGS)
*** 1822,1831 ****
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
  	MemoryContext aggcontext,
  				oldcontext;
! 	StringInfo	state;
  	Datum		val;
- 	JsonTypeCategory tcategory;
- 	Oid			outfuncoid;
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
--- 1989,1996 ----
  	Oid			val_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
  	MemoryContext aggcontext,
  				oldcontext;
! 	JsonOutContext	*out = NULL;
  	Datum		val;
  
  	if (val_type == InvalidOid)
  		ereport(ERROR,
*************** json_agg_transfn(PG_FUNCTION_ARGS)
*** 1847,1891 ****
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		state = makeStringInfo();
  		MemoryContextSwitchTo(oldcontext);
  
! 		appendStringInfoChar(state, '[');
  	}
  	else
! 	{
! 		state = (StringInfo) PG_GETARG_POINTER(0);
! 		appendStringInfoString(state, ", ");
! 	}
  
  	/* fast path for NULLs */
  	if (PG_ARGISNULL(1))
  	{
! 		datum_to_json((Datum) 0, true, state, JSONTYPE_NULL, InvalidOid, false);
! 		PG_RETURN_POINTER(state);
  	}
  
  	val = PG_GETARG_DATUM(1);
  
! 	/* XXX we do this every time?? */
! 	json_categorize_type(val_type,
! 						 &tcategory, &outfuncoid);
! 
! 	/* add some whitespace if structured type and not first item */
! 	if (!PG_ARGISNULL(0) &&
! 		(tcategory == JSONTYPE_ARRAY || tcategory == JSONTYPE_COMPOSITE))
! 	{
! 		appendStringInfoString(state, "\n ");
! 	}
  
! 	datum_to_json(val, false, state, tcategory, outfuncoid, false);
  
  	/*
  	 * The transition type for array_agg() is declared to be "internal", which
  	 * is a pass-by-value type the same size as a pointer.  So we can safely
! 	 * pass the ArrayBuildState pointer through nodeAgg.c's machinations.
  	 */
! 	PG_RETURN_POINTER(state);
  }
  
  /*
--- 2012,2049 ----
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		out = palloc(sizeof(JsonOutContext));
! 		json_out_init_context(out, JSON_OUT_USE_LINE_FEEDS);
  		MemoryContextSwitchTo(oldcontext);
  
! 		out->array_start(out);
  	}
  	else
! 		out = (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/* fast path for NULLs */
  	if (PG_ARGISNULL(1))
  	{
! 		datum_to_json((Datum) 0, true, out,
! 					  JSONTYPE_NULL, InvalidOid, InvalidOid, false);
! 		PG_RETURN_POINTER(out);
  	}
  
  	val = PG_GETARG_DATUM(1);
  
! 	if (out->agg_outfuncoid == InvalidOid)
! 		json_categorize_type(val_type,
! 							 &out->agg_tcategory, &out->agg_outfuncoid);
  
! 	datum_to_json(val, false, out, out->agg_tcategory,
! 				  val_type, out->agg_outfuncoid, false);
  
  	/*
  	 * The transition type for array_agg() is declared to be "internal", which
  	 * is a pass-by-value type the same size as a pointer.  So we can safely
! 	 * pass the ArrayBuildOut pointer through nodeAgg.c's machinations.
  	 */
! 	PG_RETURN_POINTER(out);
  }
  
  /*
*************** json_agg_transfn(PG_FUNCTION_ARGS)
*** 1894,1912 ****
  Datum
  json_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (state == NULL)
  		PG_RETURN_NULL();
  
! 	/* Else return state with appropriate array terminator added */
! 	PG_RETURN_TEXT_P(catenate_stringinfo_string(state, "]"));
  }
  
  /*
--- 2052,2071 ----
  Datum
  json_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	JsonOutContext	*out;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	out = PG_ARGISNULL(0) ? NULL : (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (out == NULL)
  		PG_RETURN_NULL();
  
! 	out->array_end(out);
! 
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out->result.data, out->result.len));
  }
  
  /*
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1920,1926 ****
  	Oid			val_type;
  	MemoryContext aggcontext,
  				oldcontext;
! 	StringInfo	state;
  	Datum		arg;
  
  	if (!AggCheckCallContext(fcinfo, &aggcontext))
--- 2079,2085 ----
  	Oid			val_type;
  	MemoryContext aggcontext,
  				oldcontext;
! 	JsonOutContext	*out = NULL;
  	Datum		arg;
  
  	if (!AggCheckCallContext(fcinfo, &aggcontext))
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1938,1953 ****
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		state = makeStringInfo();
  		MemoryContextSwitchTo(oldcontext);
  
! 		appendStringInfoString(state, "{ ");
  	}
  	else
! 	{
! 		state = (StringInfo) PG_GETARG_POINTER(0);
! 		appendStringInfoString(state, ", ");
! 	}
  
  	/*
  	 * Note: since json_object_agg() is declared as taking type "any", the
--- 2097,2110 ----
  		 * use the right context to enlarge the object if necessary.
  		 */
  		oldcontext = MemoryContextSwitchTo(aggcontext);
! 		out = palloc(sizeof(JsonOutContext));
! 		json_out_init_context(out, JSON_OUT_USE_SPACES);
  		MemoryContextSwitchTo(oldcontext);
  
! 		out->object_start(out);
  	}
  	else
! 		out = (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/*
  	 * Note: since json_object_agg() is declared as taking type "any", the
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1970,1978 ****
  
  	arg = PG_GETARG_DATUM(1);
  
! 	add_json(arg, false, state, val_type, true);
! 
! 	appendStringInfoString(state, " : ");
  
  	val_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
  
--- 2127,2133 ----
  
  	arg = PG_GETARG_DATUM(1);
  
! 	add_json(arg, false, out, val_type, true);
  
  	val_type = get_fn_expr_argtype(fcinfo->flinfo, 2);
  
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1986,1994 ****
  	else
  		arg = PG_GETARG_DATUM(2);
  
! 	add_json(arg, PG_ARGISNULL(2), state, val_type, false);
  
! 	PG_RETURN_POINTER(state);
  }
  
  /*
--- 2141,2149 ----
  	else
  		arg = PG_GETARG_DATUM(2);
  
! 	add_json(arg, PG_ARGISNULL(2), out, val_type, false);
  
! 	PG_RETURN_POINTER(out);
  }
  
  /*
*************** json_object_agg_transfn(PG_FUNCTION_ARGS
*** 1997,2035 ****
  Datum
  json_object_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	StringInfo	state;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	state = PG_ARGISNULL(0) ? NULL : (StringInfo) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (state == NULL)
  		PG_RETURN_NULL();
  
! 	/* Else return state with appropriate object terminator added */
! 	PG_RETURN_TEXT_P(catenate_stringinfo_string(state, " }"));
! }
! 
! /*
!  * Helper function for aggregates: return given StringInfo's contents plus
!  * specified trailing string, as a text datum.  We need this because aggregate
!  * final functions are not allowed to modify the aggregate state.
!  */
! static text *
! catenate_stringinfo_string(StringInfo buffer, const char *addon)
! {
! 	/* custom version of cstring_to_text_with_len */
! 	int			buflen = buffer->len;
! 	int			addlen = strlen(addon);
! 	text	   *result = (text *) palloc(buflen + addlen + VARHDRSZ);
! 
! 	SET_VARSIZE(result, buflen + addlen + VARHDRSZ);
! 	memcpy(VARDATA(result), buffer->data, buflen);
! 	memcpy(VARDATA(result) + buflen, addon, addlen);
  
! 	return result;
  }
  
  /*
--- 2152,2171 ----
  Datum
  json_object_agg_finalfn(PG_FUNCTION_ARGS)
  {
! 	JsonOutContext	*out;
  
  	/* cannot be called directly because of internal-type argument */
  	Assert(AggCheckCallContext(fcinfo, NULL));
  
! 	out = PG_ARGISNULL(0) ? NULL : (JsonOutContext *) PG_GETARG_POINTER(0);
  
  	/* NULL result for no rows in, as is standard with aggregates */
! 	if (out == NULL)
  		PG_RETURN_NULL();
  
! 	out->object_end(out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out->result.data, out->result.len));
  }
  
  /*
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2041,2048 ****
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	const char *sep = "";
! 	StringInfo	result;
  	Oid			val_type;
  
  	if (nargs % 2 != 0)
--- 2177,2183 ----
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	JsonOutContext	out;
  	Oid			val_type;
  
  	if (nargs % 2 != 0)
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2051,2059 ****
  				 errmsg("argument list must have even number of elements"),
  				 errhint("The arguments of json_build_object() must consist of alternating keys and values.")));
  
! 	result = makeStringInfo();
! 
! 	appendStringInfoChar(result, '{');
  
  	for (i = 0; i < nargs; i += 2)
  	{
--- 2186,2193 ----
  				 errmsg("argument list must have even number of elements"),
  				 errhint("The arguments of json_build_object() must consist of alternating keys and values.")));
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.object_start(&out);
  
  	for (i = 0; i < nargs; i += 2)
  	{
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2064,2071 ****
  		 * here as type UNKNOWN, which fortunately does not matter to us,
  		 * since unknownout() works fine.
  		 */
- 		appendStringInfoString(result, sep);
- 		sep = ", ";
  
  		/* process key */
  		val_type = get_fn_expr_argtype(fcinfo->flinfo, i);
--- 2198,2203 ----
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2084,2092 ****
  
  		arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, false, result, val_type, true);
! 
! 		appendStringInfoString(result, " : ");
  
  		/* process value */
  		val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1);
--- 2216,2222 ----
  
  		arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, false, &out, val_type, true);
  
  		/* process value */
  		val_type = get_fn_expr_argtype(fcinfo->flinfo, i + 1);
*************** json_build_object(PG_FUNCTION_ARGS)
*** 2102,2113 ****
  		else
  			arg = PG_GETARG_DATUM(i + 1);
  
! 		add_json(arg, PG_ARGISNULL(i + 1), result, val_type, false);
  	}
  
! 	appendStringInfoChar(result, '}');
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 2232,2243 ----
  		else
  			arg = PG_GETARG_DATUM(i + 1);
  
! 		add_json(arg, PG_ARGISNULL(i + 1), &out, val_type, false);
  	}
  
! 	out.object_end(&out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_build_array(PG_FUNCTION_ARGS)
*** 2128,2140 ****
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	const char *sep = "";
! 	StringInfo	result;
  	Oid			val_type;
  
! 	result = makeStringInfo();
! 
! 	appendStringInfoChar(result, '[');
  
  	for (i = 0; i < nargs; i++)
  	{
--- 2258,2268 ----
  	int			nargs = PG_NARGS();
  	int			i;
  	Datum		arg;
! 	JsonOutContext	out;
  	Oid			val_type;
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.array_start(&out);
  
  	for (i = 0; i < nargs; i++)
  	{
*************** json_build_array(PG_FUNCTION_ARGS)
*** 2145,2153 ****
  		 * here as type UNKNOWN, which fortunately does not matter to us,
  		 * since unknownout() works fine.
  		 */
- 		appendStringInfoString(result, sep);
- 		sep = ", ";
- 
  		val_type = get_fn_expr_argtype(fcinfo->flinfo, i);
  
  		if (val_type == InvalidOid)
--- 2273,2278 ----
*************** json_build_array(PG_FUNCTION_ARGS)
*** 2161,2172 ****
  		else
  			arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, PG_ARGISNULL(i), result, val_type, false);
  	}
  
! 	appendStringInfoChar(result, ']');
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len));
  }
  
  /*
--- 2286,2297 ----
  		else
  			arg = PG_GETARG_DATUM(i);
  
! 		add_json(arg, PG_ARGISNULL(i), &out, val_type, false);
  	}
  
! 	out.array_end(&out);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_object(PG_FUNCTION_ARGS)
*** 2189,2202 ****
  {
  	ArrayType  *in_array = PG_GETARG_ARRAYTYPE_P(0);
  	int			ndims = ARR_NDIM(in_array);
! 	StringInfoData result;
  	Datum	   *in_datums;
  	bool	   *in_nulls;
  	int			in_count,
  				count,
  				i;
- 	text	   *rval;
- 	char	   *v;
  
  	switch (ndims)
  	{
--- 2314,2325 ----
  {
  	ArrayType  *in_array = PG_GETARG_ARRAYTYPE_P(0);
  	int			ndims = ARR_NDIM(in_array);
! 	JsonOutContext	out;
  	Datum	   *in_datums;
  	bool	   *in_nulls;
  	int			in_count,
  				count,
  				i;
  
  	switch (ndims)
  	{
*************** json_object(PG_FUNCTION_ARGS)
*** 2230,2238 ****
  
  	count = in_count / 2;
  
! 	initStringInfo(&result);
! 
! 	appendStringInfoChar(&result, '{');
  
  	for (i = 0; i < count; ++i)
  	{
--- 2353,2360 ----
  
  	count = in_count / 2;
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.object_start(&out);
  
  	for (i = 0; i < count; ++i)
  	{
*************** json_object(PG_FUNCTION_ARGS)
*** 2241,2272 ****
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		v = TextDatumGetCString(in_datums[i * 2]);
! 		if (i > 0)
! 			appendStringInfoString(&result, ", ");
! 		escape_json(&result, v);
! 		appendStringInfoString(&result, " : ");
! 		pfree(v);
  		if (in_nulls[i * 2 + 1])
! 			appendStringInfoString(&result, "null");
  		else
! 		{
! 			v = TextDatumGetCString(in_datums[i * 2 + 1]);
! 			escape_json(&result, v);
! 			pfree(v);
! 		}
  	}
  
! 	appendStringInfoChar(&result, '}');
  
  	pfree(in_datums);
  	pfree(in_nulls);
  
! 	rval = cstring_to_text_with_len(result.data, result.len);
! 	pfree(result.data);
! 
! 	PG_RETURN_TEXT_P(rval);
! 
  }
  
  /*
--- 2363,2385 ----
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		out.key(&out, in_datums[i * 2], JSONTYPE_OTHER,
! 				TEXTOID, 47 /* textout */);
! 
  		if (in_nulls[i * 2 + 1])
! 			out.value(&out, (Datum) 0, JSONTYPE_NULL,
! 					  InvalidOid, InvalidOid);
  		else
! 			out.value(&out, in_datums[i * 2 + 1], JSONTYPE_OTHER,
! 					  TEXTOID, 47 /* textout */);
  	}
  
! 	out.object_end(&out);
  
  	pfree(in_datums);
  	pfree(in_nulls);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  /*
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2282,2288 ****
  	ArrayType  *val_array = PG_GETARG_ARRAYTYPE_P(1);
  	int			nkdims = ARR_NDIM(key_array);
  	int			nvdims = ARR_NDIM(val_array);
! 	StringInfoData result;
  	Datum	   *key_datums,
  			   *val_datums;
  	bool	   *key_nulls,
--- 2395,2401 ----
  	ArrayType  *val_array = PG_GETARG_ARRAYTYPE_P(1);
  	int			nkdims = ARR_NDIM(key_array);
  	int			nvdims = ARR_NDIM(val_array);
! 	JsonOutContext	out;
  	Datum	   *key_datums,
  			   *val_datums;
  	bool	   *key_nulls,
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2290,2297 ****
  	int			key_count,
  				val_count,
  				i;
- 	text	   *rval;
- 	char	   *v;
  
  	if (nkdims > 1 || nkdims != nvdims)
  		ereport(ERROR,
--- 2403,2408 ----
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2314,2322 ****
  				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
  				 errmsg("mismatched array dimensions")));
  
! 	initStringInfo(&result);
! 
! 	appendStringInfoChar(&result, '{');
  
  	for (i = 0; i < key_count; ++i)
  	{
--- 2425,2432 ----
  				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
  				 errmsg("mismatched array dimensions")));
  
! 	json_out_init_context(&out, JSON_OUT_USE_SPACES);
! 	out.object_start(&out);
  
  	for (i = 0; i < key_count; ++i)
  	{
*************** json_object_two_arg(PG_FUNCTION_ARGS)
*** 2325,2357 ****
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		v = TextDatumGetCString(key_datums[i]);
! 		if (i > 0)
! 			appendStringInfoString(&result, ", ");
! 		escape_json(&result, v);
! 		appendStringInfoString(&result, " : ");
! 		pfree(v);
  		if (val_nulls[i])
! 			appendStringInfoString(&result, "null");
  		else
! 		{
! 			v = TextDatumGetCString(val_datums[i]);
! 			escape_json(&result, v);
! 			pfree(v);
! 		}
  	}
  
! 	appendStringInfoChar(&result, '}');
  
  	pfree(key_datums);
  	pfree(key_nulls);
  	pfree(val_datums);
  	pfree(val_nulls);
  
! 	rval = cstring_to_text_with_len(result.data, result.len);
! 	pfree(result.data);
! 
! 	PG_RETURN_TEXT_P(rval);
  }
  
  
--- 2435,2459 ----
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  					 errmsg("null value not allowed for object key")));
  
! 		out.key(&out, key_datums[i], JSONTYPE_OTHER,
! 				TEXTOID, 47 /* textout */);
! 
  		if (val_nulls[i])
! 			out.value(&out, (Datum) 0, JSONTYPE_NULL,
! 					  InvalidOid, InvalidOid);
  		else
! 			out.value(&out, val_datums[i], JSONTYPE_OTHER,
! 					  TEXTOID, 47 /* textout */);
  	}
  
! 	out.object_end(&out);
  
  	pfree(key_datums);
  	pfree(key_nulls);
  	pfree(val_datums);
  	pfree(val_nulls);
  
! 	PG_RETURN_TEXT_P(cstring_to_text_with_len(out.result.data, out.result.len));
  }
  
  
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
new file mode 100644
index 26fcc5b..f485d37
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 15,20 ****
--- 15,21 ----
  
  #include "executor/executor.h"
  #include "lib/stringinfo.h"
+ #include "utils/jsonapi.h"
  
  typedef enum ExplainFormat
  {
*************** typedef struct ExplainState
*** 42,47 ****
--- 43,49 ----
  	int			indent;			/* current indentation level */
  	List	   *grouping_stack; /* format-specific grouping state */
  	List	   *deparse_cxt;	/* context list for deparsing expressions */
+ 	JsonOutContext	json_cxt;	/* JSON output context */
  } ExplainState;
  
  /* Hook for plugins to get control in ExplainOneQuery() */
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
new file mode 100644
index 296d20a..f31332c
*** a/src/include/utils/jsonapi.h
--- b/src/include/utils/jsonapi.h
*************** extern JsonLexContext *makeJsonLexContex
*** 124,127 ****
--- 124,202 ----
   */
  extern bool IsValidJsonNumber(const char *str, int len);
  
+ 
+ /*
+  * Generalized structures for producing JSON output.
+  */
+ typedef enum					/* type categories for datum_to_json */
+ {
+ 	JSONTYPE_NULL,				/* null, so we didn't bother to identify */
+ 	JSONTYPE_BOOL,				/* boolean (built-in types only) */
+ 	JSONTYPE_NUMERIC,			/* numeric (ditto) */
+ 	JSONTYPE_DATE,				/* we use special formatting for datetimes */
+ 	JSONTYPE_TIMESTAMP,
+ 	JSONTYPE_TIMESTAMPTZ,
+ 	JSONTYPE_JSON,				/* JSON itself (and JSONB) */
+ 	JSONTYPE_ARRAY,				/* array */
+ 	JSONTYPE_COMPOSITE,			/* composite */
+ 	JSONTYPE_CAST,				/* something with an explicit cast to JSON */
+ 	JSONTYPE_OTHER				/* all else */
+ } JsonTypeCategory;
+ 
+ struct JsonOutContext;
+ 
+ typedef void (*json_out_struct_action)(struct JsonOutContext *out);
+ typedef void (*json_out_key_value_action)(struct JsonOutContext *out,
+ 			  Datum key, JsonTypeCategory tcategory,
+ 			  Oid typoid, Oid outfuncoid);
+ 
+ #define JSON_OUT_USE_LINE_FEEDS		1
+ #define JSON_OUT_USE_SPACES			2
+ 
+ typedef struct JsonOutContext {
+ 	json_out_struct_action		object_start;
+ 	json_out_struct_action		object_end;
+ 	json_out_struct_action		array_start;
+ 	json_out_struct_action		array_end;
+ 	json_out_struct_action		before_key;
+ 	json_out_key_value_action	key;
+ 	json_out_struct_action		after_key;
+ 	json_out_struct_action		before_value;
+ 	json_out_key_value_action	value;
+ 	json_out_struct_action		after_value;
+ 
+ 	StringInfoData	result;
+ 	int		flags;
+ 	int		indent_spaces;
+ 	bool	need_comma;
+ 	bool	need_colon;
+ 	int		depth;
+ 	/* these are used in json_agg to cache the type information */
+ 	JsonTypeCategory	agg_tcategory;
+ 	Oid					agg_outfuncoid;
+ } JsonOutContext;
+ 
+ extern void json_out_init_context(JsonOutContext *out, int flags);
+ extern void json_out_object_start(JsonOutContext *out);
+ extern void json_out_object_end(JsonOutContext *out);
+ extern void json_out_array_start(JsonOutContext *out);
+ extern void json_out_array_end(JsonOutContext *out);
+ extern void json_out_before_key(JsonOutContext *out);
+ extern void json_out_key(JsonOutContext *out, Datum val,
+ 				JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid);
+ extern void json_out_after_key(JsonOutContext *out);
+ extern void json_out_before_value(JsonOutContext *out);
+ extern void json_out_value(JsonOutContext *out, Datum val,
+ 				JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid);
+ extern void json_out_after_value(JsonOutContext *out);
+ 
+ /* cstring helpers */
+ extern void json_out_cstring_key(JsonOutContext *out, const char *str);
+ extern void json_out_cstring_value(JsonOutContext *out, const char *str);
+ 
+ extern void composite_to_json(Datum composite, JsonOutContext *out);
+ extern void datum_to_json(Datum val, bool is_null, JsonOutContext *out,
+ 			  JsonTypeCategory tcategory, Oid typoid, Oid outfuncoid,
+ 			  bool key_scalar);
+ 
  #endif   /* JSONAPI_H */
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
new file mode 100644
index eca9690..203f334
*** a/src/test/regress/expected/insert_conflict.out
--- b/src/test/regress/expected/insert_conflict.out
*************** explain (costs off, format json) insert
*** 190,196 ****
         "Relation Name": "insertconflicttest",                          +
         "Alias": "insertconflicttest",                                  +
         "Conflict Resolution": "UPDATE",                                +
!        "Conflict Arbiter Indexes": ["key_index"],                      +
         "Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+
         "Plans": [                                                      +
           {                                                             +
--- 190,198 ----
         "Relation Name": "insertconflicttest",                          +
         "Alias": "insertconflicttest",                                  +
         "Conflict Resolution": "UPDATE",                                +
!        "Conflict Arbiter Indexes": [                                   +
!          "key_index"                                                   +
!        ],                                                              +
         "Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+
         "Plans": [                                                      +
           {                                                             +
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
new file mode 100644
index 3942c3b..fbc9b5b
*** a/src/test/regress/expected/json.out
--- b/src/test/regress/expected/json.out
*************** SELECT json_agg(q)
*** 457,476 ****
                 ROW(y.*,ARRAY[4,5,6])] AS z
           FROM generate_series(1,2) x,
                generate_series(4,5) y) q;
!                                json_agg                                
! -----------------------------------------------------------------------
!  [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]}, +
!   {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}, +
!   {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]}, +
    {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}]
  (1 row)
  
  SELECT json_agg(q)
    FROM rows q;
!        json_agg        
! -----------------------
!  [{"x":1,"y":"txt1"}, +
!   {"x":2,"y":"txt2"}, +
    {"x":3,"y":"txt3"}]
  (1 row)
  
--- 457,476 ----
                 ROW(y.*,ARRAY[4,5,6])] AS z
           FROM generate_series(1,2) x,
                generate_series(4,5) y) q;
!                                json_agg                               
! ----------------------------------------------------------------------
!  [{"b":"a1","c":4,"z":[{"f1":1,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+
!   {"b":"a1","c":5,"z":[{"f1":1,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]},+
!   {"b":"a2","c":4,"z":[{"f1":2,"f2":[1,2,3]},{"f1":4,"f2":[4,5,6]}]},+
    {"b":"a2","c":5,"z":[{"f1":2,"f2":[1,2,3]},{"f1":5,"f2":[4,5,6]}]}]
  (1 row)
  
  SELECT json_agg(q)
    FROM rows q;
!        json_agg       
! ----------------------
!  [{"x":1,"y":"txt1"},+
!   {"x":2,"y":"txt2"},+
    {"x":3,"y":"txt3"}]
  (1 row)
  
*************** SELECT json_build_array('a',1,'b',1.2,'c
*** 1505,1522 ****
  (1 row)
  
  SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
!                              json_build_object                              
! ----------------------------------------------------------------------------
!  {"a" : 1, "b" : 1.2, "c" : true, "d" : null, "e" : {"x": 3, "y": [1,2,3]}}
  (1 row)
  
  SELECT json_build_object(
         'a', json_build_object('b',false,'c',99),
         'd', json_build_object('e',array[9,8,7]::int[],
             'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r)));
!                                         json_build_object                                        
! -------------------------------------------------------------------------------------------------
!  {"a" : {"b" : false, "c" : 99}, "d" : {"e" : [9,8,7], "f" : {"relkind":"r","name":"pg_class"}}}
  (1 row)
  
  -- empty objects/arrays
--- 1505,1522 ----
  (1 row)
  
  SELECT json_build_object('a',1,'b',1.2,'c',true,'d',null,'e',json '{"x": 3, "y": [1,2,3]}');
!                            json_build_object                           
! -----------------------------------------------------------------------
!  {"a": 1, "b": 1.2, "c": true, "d": null, "e": {"x": 3, "y": [1,2,3]}}
  (1 row)
  
  SELECT json_build_object(
         'a', json_build_object('b',false,'c',99),
         'd', json_build_object('e',array[9,8,7]::int[],
             'f', (select row_to_json(r) from ( select relkind, oid::regclass as name from pg_class where relname = 'pg_class') r)));
!                                      json_build_object                                     
! -------------------------------------------------------------------------------------------
!  {"a": {"b": false, "c": 99}, "d": {"e": [9,8,7], "f": {"relkind":"r","name":"pg_class"}}}
  (1 row)
  
  -- empty objects/arrays
*************** SELECT json_build_object();
*** 1536,1542 ****
  SELECT json_build_object(1,2);
   json_build_object 
  -------------------
!  {"1" : 2}
  (1 row)
  
  -- keys must be scalar and not null
--- 1536,1542 ----
  SELECT json_build_object(1,2);
   json_build_object 
  -------------------
!  {"1": 2}
  (1 row)
  
  -- keys must be scalar and not null
*************** INSERT INTO foo VALUES (847002,'t16','GE
*** 1555,1578 ****
  INSERT INTO foo VALUES (847003,'sub-alpha','GESS90');
  SELECT json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type)))
  FROM foo;
!                                                                             json_build_object                                                                            
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  {"turbines" : { "847001" : {"name" : "t15", "type" : "GE1043"}, "847002" : {"name" : "t16", "type" : "GE1043"}, "847003" : {"name" : "sub-alpha", "type" : "GESS90"} }}
  (1 row)
  
  -- json_object
  -- one dimension
  SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}');
!                       json_object                      
! -------------------------------------------------------
!  {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"}
  (1 row)
  
  -- same but with two dimensions
  SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}');
!                       json_object                      
! -------------------------------------------------------
!  {"a" : "1", "b" : "2", "3" : null, "d e f" : "a b c"}
  (1 row)
  
  -- odd number error
--- 1555,1578 ----
  INSERT INTO foo VALUES (847003,'sub-alpha','GESS90');
  SELECT json_build_object('turbines',json_object_agg(serial_num,json_build_object('name',name,'type',type)))
  FROM foo;
!                                                                       json_build_object                                                                      
! -------------------------------------------------------------------------------------------------------------------------------------------------------------
!  {"turbines": {"847001": {"name": "t15", "type": "GE1043"}, "847002": {"name": "t16", "type": "GE1043"}, "847003": {"name": "sub-alpha", "type": "GESS90"}}}
  (1 row)
  
  -- json_object
  -- one dimension
  SELECT json_object('{a,1,b,2,3,NULL,"d e f","a b c"}');
!                     json_object                    
! ---------------------------------------------------
!  {"a": "1", "b": "2", "3": null, "d e f": "a b c"}
  (1 row)
  
  -- same but with two dimensions
  SELECT json_object('{{a,1},{b,2},{3,NULL},{"d e f","a b c"}}');
!                     json_object                    
! ---------------------------------------------------
!  {"a": "1", "b": "2", "3": null, "d e f": "a b c"}
  (1 row)
  
  -- odd number error
*************** SELECT json_object('{{{a,b},{c,d}},{{b,c
*** 1589,1597 ****
  ERROR:  wrong number of array subscripts
  --two argument form of json_object
  select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}');
!                      json_object                      
! ------------------------------------------------------
!  {"a" : "1", "b" : "2", "c" : "3", "d e f" : "a b c"}
  (1 row)
  
  -- too many dimensions
--- 1589,1597 ----
  ERROR:  wrong number of array subscripts
  --two argument form of json_object
  select json_object('{a,b,c,"d e f"}','{1,2,3,"a b c"}');
!                    json_object                    
! --------------------------------------------------
!  {"a": "1", "b": "2", "c": "3", "d e f": "a b c"}
  (1 row)
  
  -- too many dimensions
*************** select json_object('{a,b,NULL,"d e f"}',
*** 1607,1615 ****
  ERROR:  null value not allowed for object key
  -- empty key is allowed
  select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
!                      json_object                     
! -----------------------------------------------------
!  {"a" : "1", "b" : "2", "" : "3", "d e f" : "a b c"}
  (1 row)
  
  -- json_to_record and json_to_recordset
--- 1607,1615 ----
  ERROR:  null value not allowed for object key
  -- empty key is allowed
  select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
!                    json_object                   
! -------------------------------------------------
!  {"a": "1", "b": "2", "": "3", "d e f": "a b c"}
  (1 row)
  
  -- json_to_record and json_to_recordset
-- 
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