Fix hstore_to_json_loose's detection of valid JSON number values.
authorAndrew Dunstan
Mon, 1 Dec 2014 16:28:45 +0000 (11:28 -0500)
committerAndrew Dunstan
Mon, 1 Dec 2014 16:44:48 +0000 (11:44 -0500)
We expose a function IsValidJsonNumber that internally calls the lexer
for json numbers. That allows us to use the same test everywhere,
instead of inventing a broken test for hstore conversions. The new
function is also used in datum_to_json, replacing the code that is now
moved to the new function.

Backpatch to 9.3 where hstore_to_json_loose was introduced.

contrib/hstore/hstore_io.c
src/backend/utils/adt/json.c
src/include/utils/jsonapi.h

index 4e6a54b775e0215f60614d9acbe707edc0d5865e..932061939167752a0bddb46898050b713ff95052 100644 (file)
@@ -12,6 +12,7 @@
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
 #include "utils/json.h"
+#include "utils/jsonapi.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/typcache.h"
@@ -1253,7 +1254,6 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
    int         count = HS_COUNT(in);
    char       *base = STRPTR(in);
    HEntry     *entries = ARRPTR(in);
-   bool        is_number;
    StringInfoData tmp,
                dst;
 
@@ -1280,48 +1280,9 @@ hstore_to_json_loose(PG_FUNCTION_ARGS)
            appendStringInfoString(&dst, "false");
        else
        {
-           is_number = false;
            resetStringInfo(&tmp);
            appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i));
-
-           /*
-            * don't treat something with a leading zero followed by another
-            * digit as numeric - could be a zip code or similar
-            */
-           if (tmp.len > 0 &&
-               !(tmp.data[0] == '0' &&
-                 isdigit((unsigned char) tmp.data[1])) &&
-               strspn(tmp.data, "+-0123456789Ee.") == tmp.len)
-           {
-               /*
-                * might be a number. See if we can input it as a numeric
-                * value. Ignore any actual parsed value.
-                */
-               char       *endptr = "junk";
-               long        lval;
-
-               lval = strtol(tmp.data, &endptr, 10);
-               (void) lval;
-               if (*endptr == '\0')
-               {
-                   /*
-                    * strol man page says this means the whole string is
-                    * valid
-                    */
-                   is_number = true;
-               }
-               else
-               {
-                   /* not an int - try a double */
-                   double      dval;
-
-                   dval = strtod(tmp.data, &endptr);
-                   (void) dval;
-                   if (*endptr == '\0')
-                       is_number = true;
-               }
-           }
-           if (is_number)
+           if (IsValidJsonNumber(tmp.data, tmp.len))
                appendBinaryStringInfo(&dst, tmp.data, tmp.len);
            else
                escape_json(&dst, tmp.data);
index 53a011d50b48e4b77915d4d160ac37779080b4e3..0873bb3ce15c11a3dd08bb11ab2877ff403b8758 100644 (file)
@@ -164,6 +164,36 @@ lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token)
     (c) == '_' || \
     IS_HIGHBIT_SET(c))
 
+/* utility function to check if a string is a valid JSON number */
+extern bool
+IsValidJsonNumber(const char * str, int len)
+{
+   bool        numeric_error;
+   JsonLexContext dummy_lex;
+
+
+   /*
+    * json_lex_number expects a leading  '-' to have been eaten already.
+    *
+    * having to cast away the constness of str is ugly, but there's not much
+    * easy alternative.
+    */
+   if (*str == '-')
+   {
+       dummy_lex.input = (char *) str + 1;
+       dummy_lex.input_length = len - 1;
+   }
+   else
+   {
+       dummy_lex.input = (char *) str;
+       dummy_lex.input_length = len;
+   }
+
+   json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
+
+   return ! numeric_error;
+}
+
 /*
  * Input.
  */
@@ -1306,8 +1336,6 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 {
    char       *outputstr;
    text       *jsontext;
-   bool       numeric_error;
-   JsonLexContext dummy_lex;
 
    if (is_null)
    {
@@ -1332,12 +1360,10 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
        case JSONTYPE_NUMERIC:
            outputstr = OidOutputFunctionCall(outfuncoid, val);
            /*
-            * Don't call escape_json here if it's a valid JSON number.
+            * Don't call escape_json for a non-key if it's a valid JSON
+            * number.
             */
-           dummy_lex.input = *outputstr == '-' ? outputstr + 1 : outputstr;
-           dummy_lex.input_length = strlen(dummy_lex.input);
-           json_lex_number(&dummy_lex, dummy_lex.input, &numeric_error);
-           if (! numeric_error)
+           if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr)))
                appendStringInfoString(result, outputstr);
            else
                escape_json(result, outputstr);
index e25a0d93d89aa81125b776c50580b27bac39c0a4..dcfe71c29dd093241144f6f7601f72bee73a6420 100644 (file)
@@ -107,4 +107,11 @@ extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem);
  */
 extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes);
 
+/*
+ * Utility function to check if a string is a valid JSON number.
+ *
+ * str agrument does not need to be nul-terminated.
+ */
+extern bool IsValidJsonNumber(const char * str, int len);
+
 #endif   /* JSONAPI_H */