Skip to content

Commit

Permalink
Storage: Storage.writeJSON now skips quotes on alphanumeric field nam…
Browse files Browse the repository at this point in the history
…es to speed up settings read/write

see #2429 (comment)
  • Loading branch information
gfwilliams committed Nov 23, 2023
1 parent bbceb23 commit 59dda13
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 18 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Fix `Got [ERASED] expecting X` when storage compacts while executing a function *in* storage on non-Bangle devices (fix #2431)
JIT: Fix crashes when code got too big for simple 2-byte jump instructions (fix #2433)
JIT: Fixed/re-added while loops (and DO for huge loops)
Storage: Storage.writeJSON now skips quotes on alphanumeric field names to speed up settings read/write

2v19 : Fix Object.values/entries for numeric keys after 2v18 regression (fix #2375)
nRF52: for SD>5 use static buffers for advertising and scan response data (#2367)
Expand Down
23 changes: 16 additions & 7 deletions src/jswrap_json.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,17 @@ JsVar *jswrap_json_stringify(JsVar *v, JsVar *replacer, JsVar *space) {
}


JsVar *jswrap_json_parse_internal() {
/* Parse JSON from the current lexer. unquoted fields aren't normally allowed,
but if flags&JSON_DROP_QUOTES we'll allow them */
JsVar *jswrap_json_parse_internal(JSONFlags flags) {
switch (lex->tk) {
case LEX_R_TRUE: jslGetNextToken(); return jsvNewFromBool(true);
case LEX_R_FALSE: jslGetNextToken(); return jsvNewFromBool(false);
case LEX_R_NULL: jslGetNextToken(); return jsvNewWithFlags(JSV_NULL);
case '-': {
jslGetNextToken();
if (lex->tk!=LEX_INT && lex->tk!=LEX_FLOAT) return 0;
JsVar *v = jswrap_json_parse_internal();
JsVar *v = jswrap_json_parse_internal(flags);
JsVar *zero = jsvNewFromInteger(0);
JsVar *r = jsvMathsOp(zero, v, '-');
jsvUnLock2(v, zero);
Expand Down Expand Up @@ -117,7 +119,7 @@ JsVar *jswrap_json_parse_internal() {
JsVar *arr = jsvNewEmptyArray(); if (!arr) return 0;
jslGetNextToken(); // [
while (lex->tk != ']' && !jspHasError()) {
JsVar *value = jswrap_json_parse_internal();
JsVar *value = jswrap_json_parse_internal(flags);
if (!value ||
(lex->tk!=']' && !jslMatch(','))) {
jsvUnLock2(value, arr);
Expand All @@ -135,12 +137,16 @@ JsVar *jswrap_json_parse_internal() {
case '{': {
JsVar *obj = jsvNewObject(); if (!obj) return 0;
jslGetNextToken(); // {
while (lex->tk == LEX_STR && !jspHasError()) {
while (lex->tk == LEX_STR || lex->tk == LEX_ID && !jspHasError()) {
if (!(flags&JSON_DROP_QUOTES)) {
jslMatch(LEX_STR);
return obj;
}
JsVar *key = jsvAsArrayIndexAndUnLock(jslGetTokenValueAsVar());
jslGetNextToken();
JsVar *value = 0;
if (!jslMatch(':') ||
!(value=jswrap_json_parse_internal()) ||
!(value=jswrap_json_parse_internal(flags)) ||
(lex->tk!='}' && !jslMatch(','))) {
jsvUnLock3(key, value, obj);
return 0;
Expand Down Expand Up @@ -178,17 +184,20 @@ Parse the given JSON string into a JavaScript object
NOTE: This implementation uses eval() internally, and as such it is unsafe as it
can allow arbitrary JS commands to be executed.
*/
JsVar *jswrap_json_parse(JsVar *v) {
JsVar *jswrap_json_parse_ext(JsVar *v, JSONFlags flags) {
JsLex lex;
JsVar *str = jsvAsString(v);
JsLex *oldLex = jslSetLex(&lex);
jslInit(str);
jsvUnLock(str);
JsVar *res = jswrap_json_parse_internal();
JsVar *res = jswrap_json_parse_internal(flags);
jslKill();
jslSetLex(oldLex);
return res;
}
JsVar *jswrap_json_parse(JsVar *v) {
return jswrap_json_parse_ext(v, 0);
}

/* This is like jsfGetJSONWithCallback, but handles ONLY functions (and does not print the initial 'function' text) */
void jsfGetJSONForFunctionWithCallback(JsVar *var, JSONFlags flags, vcbprintf_callback user_callback, void *user_data) {
Expand Down
10 changes: 5 additions & 5 deletions src/jswrap_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@

#include "jsvar.h"

JsVar *jswrap_json_stringify(JsVar *v, JsVar *replacer, JsVar *space);
JsVar *jswrap_json_parse_ext(JsVar *v, bool throwExceptions);
JsVar *jswrap_json_parse(JsVar *v);

typedef enum {
JSON_NONE,
JSON_SOME_NEWLINES = 1, //< insert newlines in non-simple arrays and objects
Expand All @@ -31,7 +27,7 @@ typedef enum {
JSON_NO_UNDEFINED = 64, //< don't output undefined keys in objects, and use null for undefined in arrays
JSON_ARRAYBUFFER_AS_ARRAY = 128, //< dump arraybuffers as arrays
JSON_SHOW_OBJECT_NAMES = 256, //< Show 'Promise {}'/etc for objects if the type is global
JSON_DROP_QUOTES = 512, //< When outputting objects, drop quotes for alphanumeric field names
JSON_DROP_QUOTES = 512, //< When outputting objects, drop quotes for alphanumeric field names (or allow unqouted when parsing)
JSON_PIN_TO_STRING = 1024, //< Convert pins to Strings
JSON_ALL_UNICODE_ESCAPE = 2048, //< Only use unicode \xXXXX for escape characters, not \xXX or \X
JSON_NO_NAN = 4096, //< Don't output NaN for NaN numbers, only 'null'
Expand All @@ -41,6 +37,10 @@ typedef enum {
JSON_INDENT = 16384, // MUST BE THE LAST ENTRY IN JSONFlags - we use this to count the amount of indents
} JSONFlags;

JsVar *jswrap_json_stringify(JsVar *v, JsVar *replacer, JsVar *space);
JsVar *jswrap_json_parse_ext(JsVar *v, JSONFlags flags);
JsVar *jswrap_json_parse(JsVar *v);

/* This is like jsfGetJSONWithCallback, but handles ONLY functions (and does not print the initial 'function' text) */
void jsfGetJSONForFunctionWithCallback(JsVar *var, JSONFlags flags, vcbprintf_callback user_callback, void *user_data);
/* Dump to JSON, using the given callbacks for printing data
Expand Down
14 changes: 8 additions & 6 deletions src/jswrap_storage.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ created with `require("Storage").open(filename, ...)`
JsVar *jswrap_storage_readJSON(JsVar *name, bool noExceptions) {
JsVar *v = jsfReadFile(jsfNameFromVar(name),0,0);
if (!v) return 0;
JsVar *r = jswrap_json_parse(v);
JsVar *r = jswrap_json_parse_ext(v, JSON_DROP_QUOTES);
jsvUnLock(v);
if (noExceptions) {
jsvUnLock(jspGetException());
Expand Down Expand Up @@ -288,23 +288,25 @@ disappear when the device resets or power is lost.
Simply write `require("Storage").writeJSON("MyFile", [1,2,3])` to write a new
file, and `require("Storage").readJSON("MyFile")` to read it.
This is (almost) equivalent to: `require("Storage").write(name, JSON.stringify(data))`
This is (almost) equivalent to `require("Storage").write(name, JSON.stringify(data))` (see the notes below)
**Note:** This function should be used with normal files, and not `StorageFile`s
created with `require("Storage").open(filename, ...)`
**Note:** Normally `JSON.stringify` converts any non-standard character to an escape code with `\uXXXX`, but
as of Espruino 2v20, when writing to a file we use the most compact form, like `\xXX` or `\X`. This saves
space and is faster, but also means that if a String wasn't a UTF8 string but contained characters in the UTF8 codepoint range,
when saved it won't end up getting reloaded as a UTF8 string.
as of Espruino 2v20, when writing to a file we use the most compact form, like `\xXX` or `\X`, as well as
skipping quotes on fields. This saves space and is faster, but also means that if a String wasn't a UTF8
string but contained characters in the UTF8 codepoint range, when saved it won't end up getting reloaded as a UTF8 string.
It does mean that you cannot parse the file with just `JSON.parse` as it's no longer standard JSON but is JS,
so you must use `Storage.readJSON`
*/
bool jswrap_storage_writeJSON(JsVar *name, JsVar *data) {
JsVar *d = jsvNewFromEmptyString();
if (!d) return false;
/* Don't call jswrap_json_stringify directly because we want to ensure we don't use JSON_JSON_COMPATIBILE, so
String escapes like `\xFC` stay as `\xFC` and not `\u00FC` to save space and help with unicode compatibility
*/
jsfGetJSON(data, d, (JSON_IGNORE_FUNCTIONS|JSON_NO_UNDEFINED|JSON_ARRAYBUFFER_AS_ARRAY|JSON_JSON_COMPATIBILE) &~JSON_ALL_UNICODE_ESCAPE);
jsfGetJSON(data, d, (JSON_DROP_QUOTES|JSON_IGNORE_FUNCTIONS|JSON_NO_UNDEFINED|JSON_ARRAYBUFFER_AS_ARRAY|JSON_JSON_COMPATIBILE) &~JSON_ALL_UNICODE_ESCAPE);
bool r = jsfWriteFile(jsfNameFromVar(name), d, JSFF_NONE, 0, 0);
jsvUnLock(d);
return r;
Expand Down

0 comments on commit 59dda13

Please sign in to comment.