8d925434338debb305027cfbac15e29ca0d9e3a6
angie
  Mon Jun 29 13:11:09 2015 -0700
Added support for null to jsonParse, except for when we expect to find
an int, float or boolean.

diff --git src/lib/jsonParse.c src/lib/jsonParse.c
index ad5f3de..d6d629c 100644
--- src/lib/jsonParse.c
+++ src/lib/jsonParse.c
@@ -49,30 +49,37 @@
 
 struct jsonElement *newJsonObject(struct hash *h)
 {
 struct jsonElement *ele = newJsonElement(jsonObject);
 ele->val.jeHash = h;
 return ele;
 }
 
 struct jsonElement *newJsonList(struct slRef *list)
 {
 struct jsonElement *ele = newJsonElement(jsonList);
 ele->val.jeList = list;
 return ele;
 }
 
+struct jsonElement *newJsonNull()
+{
+struct jsonElement *ele = newJsonElement(jsonNull);
+ele->val.jeNull = NULL;
+return ele;
+}
+
 void jsonObjectAdd(struct jsonElement *h, char *name, struct jsonElement *ele)
 // Add a new element to a jsonObject; existing values are replaced.
 {
 if(h->type != jsonObject)
     errAbort("jsonObjectAdd called on element with incorrect type (%d)", h->type);
 hashReplace(h->val.jeHash, name, ele);
 }
 
 void jsonListAdd(struct jsonElement *list, struct jsonElement *ele)
 {
 if(list->type != jsonList)
     errAbort("jsonListAdd called on element with incorrect type (%d)", list->type);
 slAddHead(&list->val.jeList, ele);
 }
 
@@ -199,93 +206,122 @@
         (*posPtr)++;
     else
         break;
     }
 skipLeadingSpacesWithPos(str, posPtr);
 getSpecificChar(']', str, posPtr);
 slReverse(&list);
 return newJsonList(list);
 }
 
 static struct jsonElement *jsonParseString(char *str, int *posPtr)
 {
 return newJsonString(getString(str, posPtr));
 }
 
-static struct jsonElement *jsonParseBoolean(char *str, int *posPtr)
-{
-struct jsonElement *ele = NULL;
-int i;
-for(i = 0; str[*posPtr + i] && isalpha(str[*posPtr + i]); i++)
-    ;
-char *val = cloneStringZ(str + *posPtr, i);
-if(sameString(val, "true"))
-    ele = newJsonBoolean(TRUE);
-else if(sameString(val, "false"))
-    ele =  newJsonBoolean(FALSE);
-else
-    errAbort("Invalid boolean value '%s'; pos: %d", val, *posPtr);
-*posPtr += i;
-freez(&val);
-return ele;
-}
-
 static struct jsonElement *jsonParseNumber(char *str, int *posPtr)
 {
 int i;
 boolean integral = TRUE;
 struct jsonElement *retVal = NULL;
 
 for(i = 0;; i++)
     {
     char c = str[*posPtr + i];
     if(c == 'e' || c == 'E' || c == '.')
         integral = FALSE;
     else if(!c || (!isdigit(c) && c != '-'))
         break;
     }
 char *val = cloneStringZ(str + *posPtr, i);
 *posPtr += i;
 if(integral)
     retVal = newJsonNumber(sqlLongLong(val));
 else
     {
     double d;
     if(sscanf(val, "%lf", &d))
         retVal = newJsonDouble(d);
     else
         errAbort("Invalid JSON Double: %s", val);
     }
 freez(&val);
 return retVal;
 }
 
+static boolean startsWithWordAlpha(const char *firstWord, const char *string)
+/* Return TRUE if string starts with firstWord and then either ends or continues with
+ * non-alpha characters. */
+{
+return startsWith(firstWord, string) && !isalpha(string[strlen(firstWord)]);
+}
+
+#define JSON_KEYWORD_TRUE "true"
+#define JSON_KEYWORD_FALSE "false"
+#define JSON_KEYWORD_NULL "null"
+
+static struct jsonElement *jsonParseKeyword(char *str, int *posPtr)
+/* If str+*posPtr starts with a keyword token (true, false, null), return a new
+ * jsonElement for it; otherwise return NULL. */
+{
+char *s = str + *posPtr;
+if (startsWithWordAlpha(JSON_KEYWORD_TRUE, s))
+    {
+    *posPtr += strlen(JSON_KEYWORD_TRUE);
+    return newJsonBoolean(TRUE);
+    }
+if (startsWithWordAlpha(JSON_KEYWORD_FALSE, s))
+    {
+    *posPtr += strlen(JSON_KEYWORD_FALSE);
+    return newJsonBoolean(FALSE);
+    }
+if (startsWithWordAlpha(JSON_KEYWORD_NULL, s))
+    {
+    *posPtr += strlen(JSON_KEYWORD_NULL);
+    return newJsonNull();
+    }
+return NULL;
+}
+
+// Maximum number of characters from the current position to display in error message:
+#define MAX_LEN_FOR_ERROR 100
+
 static struct jsonElement *jsonParseExpression(char *str, int *posPtr)
 {
 skipLeadingSpacesWithPos(str, posPtr);
 char c = str[*posPtr];
+struct jsonElement *ele = NULL;
 if(c == '{')
     return jsonParseObject(str, posPtr);
 else if (c == '[')
     return jsonParseList(str, posPtr);
 else if (c == '"')
     return jsonParseString(str, posPtr);
 else if (isdigit(c) || c == '-')
     return jsonParseNumber(str, posPtr);
+else if ((ele = jsonParseKeyword(str, posPtr)) != NULL)
+    return ele;
+else
+    {
+    const char *s = str + *posPtr;
+    int len = strlen(s);
+    if (len > MAX_LEN_FOR_ERROR)
+        errAbort("Invalid JSON token: %.*s...", MAX_LEN_FOR_ERROR, s);
     else
-    return jsonParseBoolean(str, posPtr);
-// XXXX support null?
+        errAbort("Invalid JSON token: %s", s);
+    }
+return NULL;
 }
 
 struct jsonElement *jsonParse(char *str)
 {
 // parse string into an in-memory json representation
 int pos = 0;
 struct jsonElement *ele = jsonParseExpression(str, &pos);
 skipLeadingSpacesWithPos(str, &pos);
 if(str[pos])
     errAbort("Invalid JSON: unprocessed trailing string at position: %d: %s", pos, str + pos);
 return ele;
 }
 
 char *jsonStringEscape(char *inString)
 /* backslash escape a string for use in a double quoted json string.
@@ -386,30 +422,31 @@
         struct slRef *el;
         if(ele->val.jeList)
             {
             for (el = ele->val.jeList; el != NULL; el = el->next)
                 {
                 struct jsonElement *val = el->val;
                 jsonFindNameRecurse(val, jName, pList);
                 }
             }
         break;
         }
     case jsonString:
     case jsonBoolean:
     case jsonNumber:
     case jsonDouble:
+    case jsonNull:
         {
         break;
         }
     default:
         {
         errAbort("jsonFindNameRecurse; invalid type: %d", ele->type);
         break;
         }
     }
 }
 
 struct slName *jsonFindName(struct jsonElement *json, char *jName)
 // Search the JSON tree to find all the values associated to the name
 // and add them to head of the list.  
 {
@@ -465,30 +502,31 @@
         if(ele->val.jeList)
             {
             for (el = ele->val.jeList; el != NULL; el = el->next)
                 {
                 struct jsonElement *val = el->val;
                 jsonElementRecurse(val, NULL, el->next == NULL, 
 		    startCallback, endCallback, context);
                 }
             }
         break;
         }
     case jsonString:
     case jsonBoolean:
     case jsonNumber:
     case jsonDouble:
+    case jsonNull:
         {
         break;
         }
     default:
         {
         errAbort("jsonElementRecurse; invalid type: %d", ele->type);
         break;
         }
     }
 if (endCallback != NULL)
     endCallback(ele, name, isLast, context);
 }
 
 void jsonPrintOneStart(struct jsonElement *ele, char *name, boolean isLast, int indent, FILE *f)
 /* Print the start of one json element - just name and maybe an opening brace or bracket.
@@ -508,73 +546,79 @@
         }
     case jsonList:
         {
 	fprintf(f, "[\n");
         break;
         }
     case jsonString:
         {
 	char *escaped = jsonStringEscape(ele->val.jeString);
 	fprintf(f, "\"%s\"",  escaped);
 	freez(&escaped);
 	break;
 	}
     case jsonBoolean:
         {
-	char *val = (ele->val.jeBoolean ? "frue" : "false");
+	char *val = (ele->val.jeBoolean ? "true" : "false");
 	fprintf(f, "%s", val);
 	break;
 	}
     case jsonNumber:
         {
 	fprintf(f, "%ld", ele->val.jeNumber);
 	break;
 	}
     case jsonDouble:
         {
 	fprintf(f, "%g", ele->val.jeDouble);
         break;
         }
+    case jsonNull:
+        {
+        fprintf(f, "null");
+        break;
+        }
     default:
         {
         errAbort("jsonPrintOneStart; invalid type: %d", ele->type);
         break;
         }
     }
 }
 
 void jsonPrintOneEnd(struct jsonElement *ele, char *name, boolean isLast, boolean indent, FILE *f)
 /* Print object end */
 {
 switch (ele->type)
     {
     case jsonObject:
         {
 	spaceOut(f, indent);
 	fprintf(f, "}");
         break;
         }
     case jsonList:
         {
 	spaceOut(f, indent);
 	fprintf(f, "]");
         break;
         }
     case jsonString:
     case jsonBoolean:
     case jsonNumber:
     case jsonDouble:
+    case jsonNull:
         break;
     default:
         {
         errAbort("jsonPrintOneEnd; invalid type: %d", ele->type);
         break;
         }
     }
 if (!isLast)
     fputc(',', f);
 fputc('\n', f);
 }
 
 struct jsonPrintContext
 /* Context for printing a JSON object nicely */
     {
@@ -600,85 +644,93 @@
 jsonPrintOneEnd(ele, name, isLast, jps->indent, jps->f);
 }
 
 void jsonPrintToFile(struct jsonElement *root, char *name, FILE *f, int indentPer)
 /* Print out JSON object and all children nicely indented to f as JSON objects. 
  * Name may be NULL.  Implemented via jsonPrintOneStart/jsonPrintOneEnd. */
 {
 struct jsonPrintContext jps = {f, 0, indentPer};
 jsonElementRecurse(root, NULL, TRUE, 
 	printIndentedNameStartCallback, printIndentedNameEndCallback, &jps);
 }
 
 /** Routines that check json type and return corresponding value. **/
 
 struct slRef *jsonListVal(struct jsonElement *ele, char *name)
-/* Enforce element is type jsonList.  Return list value */
+/* Enforce element is type jsonList or jsonNull.  Return list value, which may be NULL. */
 {
-if (ele->type != jsonList)
+if (ele->type == jsonNull)
+    return NULL;
+else if (ele->type != jsonList)
     errAbort("json element %s is not a list", name);
 return ele->val.jeList;
 }
 
 struct hash *jsonObjectVal(struct jsonElement *ele, char *name)
-/* Enforce object is type jsonObject.  Return object hash */
+/* Enforce object is type jsonObject or jsonNull.  Return object hash, which may be NULL. */
 {
-if (ele->type != jsonObject)
+if (ele->type == jsonNull)
+    return NULL;
+else if (ele->type != jsonObject)
     errAbort("json element %s is not an object", name);
 return ele->val.jeHash;
 }
 
 long jsonNumberVal(struct jsonElement *ele, char *name)
 /* Enforce element is type jsonNumber and return value. */
 {
 if (ele->type != jsonNumber)
     errAbort("json element %s is not a number", name);
 return ele->val.jeNumber;
 }
 
 double jsonDoubleVal(struct jsonElement *ele, char *name)
 /* Enforce element is type jsonDouble and return value. */
 {
 if (ele->type != jsonDouble)
     errAbort("json element %s is not a number", name);
 return ele->val.jeDouble;
 }
 
 boolean jsonBooleanVal(struct jsonElement *ele, char *name)
 /* Enforce element is type jsonBoolean and return value. */
 {
 if (ele->type != jsonBoolean)
     errAbort("json element %s is not a boolean", name);
 return ele->val.jeBoolean;
 }
 
 char *jsonStringVal(struct jsonElement *ele, char *eleName)
-/* Enforce element is type jsonString and return value. */
+/* Enforce element is type jsonString or jsonNull.  Return value, which may be NULL. */
 {
-if (ele->type != jsonString)
+if (ele->type == jsonNull)
+    return NULL;
+else if (ele->type != jsonString)
     errAbort("json element %s is not a string", eleName);
 return ele->val.jeString;
 }
 
 /** Routines that help work with json objects (bracket enclosed key/val pairs **/
 
 struct jsonElement *jsonFindNamedField(struct jsonElement *object, 
     char *objectName, char *field)
 /* Find named field of object or return NULL if not found.  Abort if object
  * is not actually an object. */
 {
 struct hash *hash = jsonObjectVal(object, objectName);
+if (hash == NULL)
+    return NULL;
 return hashFindVal(hash, field);
 }
 
 struct jsonElement *jsonMustFindNamedField(struct jsonElement *object, 
     char *objectName, char *field)
 /* Find named field of object or die trying. */
 {
 struct jsonElement *ele = jsonFindNamedField(object, objectName, field);
 if (ele == NULL)
    errAbort("Couldn't find field %s in json object %s", field, objectName);
 return ele;
 }
 
 char *jsonOptionalStringField(struct jsonElement *object, char *field, char *defaultVal)
 /* Return string valued field of object, or defaultVal if it doesn't exist. */