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. */