f2b6a945eb6990470e8a4435b980fa22aa567b11
angie
  Fri Sep 11 13:28:00 2015 -0700
When a NULL tag ("var") was passed to jsonListStart, it wasn't printing the
preceding comma if one was needed.  To make NULL tag universally supported
by everything that calls jsonWriteTag, change jsonWriteTag to always print
the preceding comma if necessary, and print the tag only if non-NULL.

diff --git src/lib/jsonWrite.c src/lib/jsonWrite.c
index 9669899..ce342fc 100644
--- src/lib/jsonWrite.c
+++ src/lib/jsonWrite.c
@@ -1,237 +1,226 @@
 /* jsonWrite - Helper routines for writing out JSON.  */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See README in this or parent directory for licensing information. */
 
 #include "common.h"
 #include "hash.h"
 #include "dystring.h"
 #include "sqlNum.h"
 #include "jsonParse.h"
 #include "jsonWrite.h"
 
 // Separator between elements; set this to "\n" to see elements on separate lines.
 // Newlines are fine in Javascript, e.g. in an embedded <script>.
 // However, unescaped \n is illegal in JSON and web browsers may reject it.
 // Web browser plugins can pretty-print JSON nicely.
 #define JW_SEP " "
 
 struct jsonWrite *jsonWriteNew()
 /* Return new empty jsonWrite struct. */
 {
 struct jsonWrite *jw;
 AllocVar(jw);
 jw->dy = dyStringNew(0);
 return jw;
 }
 
 void jsonWriteFree(struct jsonWrite **pJw)
 /* Free up a jsonWrite object. */
 {
 struct jsonWrite *jw = *pJw;
 if (jw != NULL)
     {
     dyStringFree(&jw->dy);
     freez(pJw);
     }
 }
 
 static void jsonWritePushObjStack(struct jsonWrite *jw, bool val)
 /* Push val on stack */
 {
 int stackIx = jw->stackIx + 1;
 if (stackIx >= ArraySize(jw->objStack))
     errAbort("Stack overflow in jsonWritePush");
 jw->objStack[stackIx] = val;
 jw->stackIx = stackIx;
 }
 
 static void jsonWritePopObjStack(struct jsonWrite *jw)
 /* pop object stack and just discard val. */
 {
 int stackIx = jw->stackIx - 1;
 if (stackIx < 0)
     errAbort("Stack underflow in jsonWritePopObjStack");
 jw->stackIx = stackIx;
 }
 
 INLINE void jsonWriteMaybeComma(struct jsonWrite *jw)
 /* If this is not the first item added to an object or list, write a comma. */
 {
 if (jw->objStack[jw->stackIx] != 0)
     dyStringAppend(jw->dy, ","JW_SEP);
 else
     jw->objStack[jw->stackIx] = 1;
 }
 
 void jsonWriteTag(struct jsonWrite *jw, char *var)
-/* Print out quoted tag followed by colon. Print out preceding comma if need be.  */
-{
-if (var != NULL)
+/* Print out preceding comma if necessary, and if var is non-NULL, quoted tag followed by colon. */
 {
 jsonWriteMaybeComma(jw);
+if (var != NULL)
     dyStringPrintf(jw->dy, "\"%s\": ", var);
 }
-}
 
 void jsonWriteString(struct jsonWrite *jw, char *var, char *string)
 /* Print out "var": "val".  If var is NULL, print val only.  If string is NULL, "var": null . */
 {
-if (var)
 jsonWriteTag(jw, var);
-else
-    jsonWriteMaybeComma(jw);
 if (string)
     dyStringPrintf(jw->dy, "\"%s\"", string);
 else
     dyStringAppend(jw->dy, "null");
 }
 
 void jsonWriteDateFromUnix(struct jsonWrite *jw, char *var, long long unixTimeVal)
 /* Add "var": YYYY-MM-DDT-HH:MM:SSZ given a Unix time stamp. Var may be NULL. */
 {
 struct dyString *dy = jw->dy;
 time_t timeStamp = unixTimeVal;
 struct tm tm;
 gmtime_r(&timeStamp, &tm);
 jsonWriteTag(jw, var);
 dyStringPrintf(dy, "\"%d:%02d:%02dT%02d:%02d:%02dZ\"",
     1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
 }
 
 void jsonWriteNumber(struct jsonWrite *jw, char *var, long long val)
 /* print out "var": val as number. Var may be NULL. */
 {
 struct dyString *dy = jw->dy;
 jsonWriteTag(jw, var);
 dyStringPrintf(dy, "%lld", val);
 }
 
 void jsonWriteDouble(struct jsonWrite *jw, char *var, double val)
 /* print out "var": val as number. Var may be NULL. */
 {
 struct dyString *dy = jw->dy;
 jsonWriteTag(jw, var);
 dyStringPrintf(dy, "%lf", val);
 }
 
 void jsonWriteLink(struct jsonWrite *jw, char *var, char *objRoot, char *name)
 /* Print out the jsony type link to another object.  objRoot will start and end with a '/'
  * and may have additional slashes in this usage. Var may be NULL. */
 {
 struct dyString *dy = jw->dy;
 jsonWriteTag(jw, var);
 dyStringPrintf(dy, "\"%s%s\"", objRoot, name);
 }
 
 void jsonWriteLinkNum(struct jsonWrite *jw, char *var, char *objRoot, long long id)
 /* Print out the jsony type link to another object with a numerical id.  objRoot will start 
  * and end with a '/' and may have additional slashes in this usage. Var may be NULL */
 {
 struct dyString *dy = jw->dy;
 jsonWriteTag(jw, var);
 dyStringPrintf(dy, "\"%s%lld\"", objRoot, id);
 }
 
 void jsonWriteListStart(struct jsonWrite *jw, char *var)
 /* Start an array in JSON. Var may be NULL */
 {
 struct dyString *dy = jw->dy;
 jsonWriteTag(jw, var);
 dyStringAppend(dy, "["JW_SEP);
 jsonWritePushObjStack(jw, FALSE);
 }
 
 void jsonWriteListEnd(struct jsonWrite *jw)
 /* End an array in JSON */
 {
 struct dyString *dy = jw->dy;
 dyStringAppend(dy, "]"JW_SEP);
 jsonWritePopObjStack(jw);
 }
 
 void jsonWriteObjectStart(struct jsonWrite *jw, char *var)
 /* Print start of object, preceded by tag if var is non-NULL. */
 {
-if (var)
 jsonWriteTag(jw, var);
-else
-    jsonWriteMaybeComma(jw);
 struct dyString *dy = jw->dy;
 dyStringAppend(dy, "{"JW_SEP);
 jsonWritePushObjStack(jw, FALSE);
 }
 
 void jsonWriteObjectEnd(struct jsonWrite *jw)
 /* End object in JSON */
 {
 struct dyString *dy = jw->dy;
 dyStringAppend(dy, "}"JW_SEP);
 jsonWritePopObjStack(jw);
 }
 
 void jsonWriteStringf(struct jsonWrite *jw, char *var, char *format, ...)
 /* Write "var": "val" where val is jsonStringEscape'd formatted string. */
 {
 // Since we're using jsonStringEscape(), we need to use a temporary dyString
 // instead of jw->dy.
 struct dyString *tmpDy = dyStringNew(0);
 va_list args;
 va_start(args, format);
 dyStringVaPrintf(tmpDy, format, args);
 va_end(args);
 char *escaped = jsonStringEscape(tmpDy->string);
 jsonWriteString(jw, var, escaped);
 freeMem(escaped);
 dyStringFree(&tmpDy);
 }
 
 void jsonWriteBoolean(struct jsonWrite *jw, char *var, boolean val)
 /* Write out "var": true or "var": false depending on val (no quotes around true/false). */
 {
 jsonWriteTag(jw, var);
 dyStringAppend(jw->dy, val ? "true" : "false");
 }
 
 void jsonWriteValueLabelList(struct jsonWrite *jw, char *var, struct slPair *pairList)
 /* Print out a named list of {"value": "<pair->name>", "label": "<pair->val>"} objects. */
 {
 jsonWriteListStart(jw, var);
 struct slPair *pair;
 for (pair = pairList;  pair != NULL;  pair = pair->next)
     {
     jsonWriteObjectStart(jw, NULL);
     jsonWriteString(jw, "value", pair->name);
     jsonWriteString(jw, "label", (char *)(pair->val));
     jsonWriteObjectEnd(jw);
     }
 jsonWriteListEnd(jw);
 }
 
 void jsonWriteSlNameList(struct jsonWrite *jw, char *var, struct slName *slnList)
 /* Print out a named list of strings from slnList. */
 {
 jsonWriteListStart(jw, var);
 struct slName *sln;
 for (sln = slnList;  sln != NULL;  sln = sln->next)
     jsonWriteString(jw, NULL, sln->name);
 jsonWriteListEnd(jw);
 }
 
 void jsonWriteAppend(struct jsonWrite *jwA, char *var, struct jsonWrite *jwB)
 /* Append jwB's contents to jwA's.  If jwB is non-NULL, it must be fully closed (no unclosed
  * list or object).  If var is non-NULL, write it out as a tag before appending.
  * If both var and jwB are NULL, leave jwA unchanged. */
 {
 if (jwB && jwB->stackIx)
     errAbort("jsonWriteAppend: second argument must be fully closed but its stackIx is %d not 0",
              jwB->stackIx);
-if (var)
 jsonWriteTag(jwA, var);
-else
-    jsonWriteMaybeComma(jwA);
 if (jwB)
     dyStringAppendN(jwA->dy, jwB->dy->string, jwB->dy->stringSize);
 else if (var)
     dyStringAppend(jwA->dy, "null");
 }