Fix hstore_to_json_loose's detection of valid JSON number values.
authorAndrew Dunstan <[email protected]>
Mon, 1 Dec 2014 16:28:45 +0000 (11:28 -0500)
committerAndrew Dunstan <[email protected]>
Mon, 1 Dec 2014 16:28:45 +0000 (11:28 -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 6ce3047215ddff917d6ff0d5c61e50c89b74d441..cd01b2faddb11cb5252e7684f969d4ea69222d41 100644 (file)
@@ -12,6 +12,7 @@
 #include "libpq/pqformat.h"
 #include "utils/builtins.h"
 #include "utils/json.h"
+#include "utils/jsonapi.h"
 #include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -1240,7 +1241,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;
 
@@ -1267,48 +1267,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 d2bf640e54e72541591434d0ffa8a29411d32948..a576843234ebc1678eee7a223cba1fbac4a07291 100644 (file)
@@ -173,6 +173,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.
  */
@@ -1338,8 +1368,6 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
 {
    char       *outputstr;
    text       *jsontext;
-   bool        numeric_error;
-   JsonLexContext dummy_lex;
 
    /* callers are expected to ensure that null keys are not passed in */
    Assert( ! (key_scalar && is_null));
@@ -1376,25 +1404,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
            break;
        case JSONTYPE_NUMERIC:
            outputstr = OidOutputFunctionCall(outfuncoid, val);
-           if (key_scalar)
-           {
-               /* always quote keys */
-               escape_json(result, outputstr);
-           }
+           /*
+            * 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
-           {
-               /*
-                * 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)
-                   appendStringInfoString(result, outputstr);
-               else
-                   escape_json(result, outputstr);
-           }
+               escape_json(result, outputstr);
            pfree(outputstr);
            break;
        case JSONTYPE_DATE:
index df057121a1178e3687580c8def057a65ada63cb6..4ae059bec61afc29e66f02356e75f91c83b36582 100644 (file)
@@ -117,4 +117,11 @@ extern JsonLexContext *makeJsonLexContextCstringLen(char *json,
                             int len,
                             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 */